Spaces:
Running
Running
Uploading necessary files to the Space (#1)
Browse files- Uploading necessary files to the Space (74e4c794807ed6165a70921c2f1bf681b70a2ca2)
- app.py +523 -0
- demo_inputs.json +246 -0
- modal_app.py +412 -0
- requirements.txt +0 -0
- system_prompt.txt +167 -0
- utils.py +385 -0
- vllm_inference.py +69 -0
app.py
ADDED
@@ -0,0 +1,523 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
import requests
|
4 |
+
import json
|
5 |
+
import plotly.graph_objects as go
|
6 |
+
import plotly.express as px
|
7 |
+
from fastapi import FastAPI
|
8 |
+
from typing import Dict, Any, Optional
|
9 |
+
import os
|
10 |
+
from datetime import datetime
|
11 |
+
|
12 |
+
# Custom CSS for MoneyMate branding
|
13 |
+
CUSTOM_CSS = """
|
14 |
+
.gradio-container {
|
15 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
16 |
+
font-family: 'Inter', sans-serif;
|
17 |
+
}
|
18 |
+
|
19 |
+
.main-header {
|
20 |
+
text-align: center;
|
21 |
+
color: white;
|
22 |
+
margin-bottom: 2rem;
|
23 |
+
}
|
24 |
+
|
25 |
+
.money-card {
|
26 |
+
background: rgba(255, 255, 255, 0.95);
|
27 |
+
border-radius: 15px;
|
28 |
+
padding: 1.5rem;
|
29 |
+
margin: 1rem 0;
|
30 |
+
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
|
31 |
+
backdrop-filter: blur(4px);
|
32 |
+
border: 1px solid rgba(255, 255, 255, 0.18);
|
33 |
+
}
|
34 |
+
|
35 |
+
.modal-branding {
|
36 |
+
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
37 |
+
-webkit-background-clip: text;
|
38 |
+
-webkit-text-fill-color: transparent;
|
39 |
+
font-weight: bold;
|
40 |
+
text-align: center;
|
41 |
+
margin-top: 1rem;
|
42 |
+
}
|
43 |
+
|
44 |
+
.advice-box {
|
45 |
+
background: #f8f9ff;
|
46 |
+
border-left: 4px solid #667eea;
|
47 |
+
padding: 1rem;
|
48 |
+
margin: 1rem 0;
|
49 |
+
border-radius: 8px;
|
50 |
+
}
|
51 |
+
|
52 |
+
.quick-action-btn {
|
53 |
+
background: linear-gradient(45deg, #667eea, #764ba2);
|
54 |
+
color: white;
|
55 |
+
border: none;
|
56 |
+
border-radius: 25px;
|
57 |
+
padding: 0.5rem 1rem;
|
58 |
+
margin: 0.25rem;
|
59 |
+
cursor: pointer;
|
60 |
+
transition: all 0.3s ease;
|
61 |
+
}
|
62 |
+
|
63 |
+
.quick-action-btn:hover {
|
64 |
+
transform: translateY(-2px);
|
65 |
+
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
66 |
+
}
|
67 |
+
"""
|
68 |
+
|
69 |
+
# MCP Server configuration
|
70 |
+
class MCPServer:
|
71 |
+
def __init__(self):
|
72 |
+
self.tools = {
|
73 |
+
"salary_breakdown": self.salary_breakdown,
|
74 |
+
"investment_advice": self.investment_advice,
|
75 |
+
"expense_analysis": self.expense_analysis,
|
76 |
+
"savings_goal": self.savings_goal
|
77 |
+
}
|
78 |
+
|
79 |
+
def salary_breakdown(self, salary: float, expenses: Dict[str, float]) -> Dict[str, Any]:
|
80 |
+
"""Break down salary using 50/30/20 rule with Indian context"""
|
81 |
+
needs = salary * 0.5 # 50% for needs
|
82 |
+
wants = salary * 0.3 # 30% for wants
|
83 |
+
savings = salary * 0.2 # 20% for savings/investments
|
84 |
+
|
85 |
+
return {
|
86 |
+
"breakdown": {
|
87 |
+
"needs": needs,
|
88 |
+
"wants": wants,
|
89 |
+
"savings": savings
|
90 |
+
},
|
91 |
+
"recommendations": self._get_indian_recommendations(salary)
|
92 |
+
}
|
93 |
+
|
94 |
+
def investment_advice(self, age: int, salary: float, risk_appetite: str) -> Dict[str, Any]:
|
95 |
+
"""Provide investment advice for Indian market"""
|
96 |
+
equity_percentage = min(100 - age, 80) # Age-based equity allocation
|
97 |
+
debt_percentage = 100 - equity_percentage
|
98 |
+
|
99 |
+
return {
|
100 |
+
"allocation": {
|
101 |
+
"equity": equity_percentage,
|
102 |
+
"debt": debt_percentage
|
103 |
+
},
|
104 |
+
"instruments": self._get_indian_instruments(risk_appetite)
|
105 |
+
}
|
106 |
+
|
107 |
+
def expense_analysis(self, expenses: Dict[str, float]) -> Dict[str, Any]:
|
108 |
+
"""Analyze expenses and provide optimization suggestions"""
|
109 |
+
total_expenses = sum(expenses.values())
|
110 |
+
analysis = {}
|
111 |
+
|
112 |
+
for category, amount in expenses.items():
|
113 |
+
percentage = (amount / total_expenses) * 100
|
114 |
+
analysis[category] = {
|
115 |
+
"amount": amount,
|
116 |
+
"percentage": percentage,
|
117 |
+
"status": self._categorize_expense(category, percentage)
|
118 |
+
}
|
119 |
+
|
120 |
+
return {"analysis": analysis, "suggestions": self._get_optimization_tips()}
|
121 |
+
|
122 |
+
def savings_goal(self, goal_amount: float, timeline_months: int, current_savings: float) -> Dict[str, Any]:
|
123 |
+
"""Calculate monthly savings needed for a goal"""
|
124 |
+
remaining_amount = goal_amount - current_savings
|
125 |
+
monthly_required = remaining_amount / timeline_months if timeline_months > 0 else 0
|
126 |
+
|
127 |
+
return {
|
128 |
+
"monthly_required": monthly_required,
|
129 |
+
"total_goal": goal_amount,
|
130 |
+
"timeline": timeline_months,
|
131 |
+
"feasibility": "achievable" if monthly_required < 15000 else "challenging"
|
132 |
+
}
|
133 |
+
|
134 |
+
def _get_indian_recommendations(self, salary: float) -> list:
|
135 |
+
"""Get India-specific financial recommendations"""
|
136 |
+
recommendations = [
|
137 |
+
"Build emergency fund of 6-12 months expenses",
|
138 |
+
"Start SIP in diversified equity mutual funds",
|
139 |
+
"Consider ELSS funds for tax saving under 80C",
|
140 |
+
"Open PPF account for long-term tax-free returns"
|
141 |
+
]
|
142 |
+
|
143 |
+
if salary > 50000:
|
144 |
+
recommendations.append("Consider NPS for additional retirement planning")
|
145 |
+
if salary > 100000:
|
146 |
+
recommendations.append("Explore direct equity investment after gaining knowledge")
|
147 |
+
|
148 |
+
return recommendations
|
149 |
+
|
150 |
+
def _get_indian_instruments(self, risk_appetite: str) -> list:
|
151 |
+
"""Get Indian investment instruments based on risk appetite"""
|
152 |
+
instruments = {
|
153 |
+
"conservative": ["PPF", "NSC", "FD", "Debt Mutual Funds"],
|
154 |
+
"moderate": ["Balanced Mutual Funds", "ELSS", "Gold ETF", "Corporate Bonds"],
|
155 |
+
"aggressive": ["Large Cap Funds", "Mid Cap Funds", "Small Cap Funds", "Direct Equity"]
|
156 |
+
}
|
157 |
+
return instruments.get(risk_appetite.lower(), instruments["moderate"])
|
158 |
+
|
159 |
+
def _categorize_expense(self, category: str, percentage: float) -> str:
|
160 |
+
"""Categorize expense as optimal, high, or low"""
|
161 |
+
thresholds = {
|
162 |
+
"rent": (25, 35),
|
163 |
+
"food": (15, 25),
|
164 |
+
"transport": (10, 15),
|
165 |
+
"utilities": (5, 10),
|
166 |
+
"entertainment": (5, 15)
|
167 |
+
}
|
168 |
+
|
169 |
+
if category.lower() in thresholds:
|
170 |
+
low, high = thresholds[category.lower()]
|
171 |
+
if percentage < low:
|
172 |
+
return "low"
|
173 |
+
elif percentage > high:
|
174 |
+
return "high"
|
175 |
+
return "optimal"
|
176 |
+
|
177 |
+
def _get_optimization_tips(self) -> list:
|
178 |
+
"""Get expense optimization tips"""
|
179 |
+
return [
|
180 |
+
"Use public transport or carpool to reduce transport costs",
|
181 |
+
"Cook at home more often to save on food expenses",
|
182 |
+
"Use energy-efficient appliances to reduce utility bills",
|
183 |
+
"Set a monthly entertainment budget and stick to it",
|
184 |
+
"Review and cancel unused subscriptions"
|
185 |
+
]
|
186 |
+
|
187 |
+
# Initialize MCP Server
|
188 |
+
mcp_server = MCPServer()
|
189 |
+
|
190 |
+
# Modal backend URL (replace with your actual Modal deployment URL)
|
191 |
+
MODAL_BACKEND_URL = os.getenv("MODAL_BACKEND_URL", "https://kaustubhme0--moneymate-backend-fastapi-app.modal.run")
|
192 |
+
|
193 |
+
def call_modal_backend(user_input: str, context: Dict[str, Any] = None) -> str:
|
194 |
+
"""Call Modal backend for AI-powered financial advice"""
|
195 |
+
try:
|
196 |
+
payload = {
|
197 |
+
"user_input": user_input,
|
198 |
+
"context": context or {}
|
199 |
+
}
|
200 |
+
|
201 |
+
response = requests.post(
|
202 |
+
f"{MODAL_BACKEND_URL}/financial_advice",
|
203 |
+
json=payload,
|
204 |
+
timeout=30
|
205 |
+
)
|
206 |
+
|
207 |
+
if response.status_code == 200:
|
208 |
+
return response.json().get("advice", "Unable to get advice at the moment.")
|
209 |
+
else:
|
210 |
+
return "Sorry, I'm having trouble connecting to the financial advisor. Please try again."
|
211 |
+
|
212 |
+
except requests.exceptions.RequestException as e:
|
213 |
+
return f"Connection error: {str(e)}. Please check your internet connection."
|
214 |
+
|
215 |
+
def create_salary_breakdown_chart(salary: float, needs: float, wants: float, savings: float):
|
216 |
+
"""Create a pie chart for salary breakdown"""
|
217 |
+
labels = ['Needs (50%)', 'Wants (30%)', 'Savings (20%)']
|
218 |
+
values = [needs, wants, savings]
|
219 |
+
colors = ['#ff6b6b', '#4ecdc4', '#45b7d1']
|
220 |
+
|
221 |
+
fig = go.Figure(data=[go.Pie(
|
222 |
+
labels=labels,
|
223 |
+
values=values,
|
224 |
+
hole=0.4,
|
225 |
+
marker_colors=colors,
|
226 |
+
textinfo='label+percent',
|
227 |
+
textfont_size=12
|
228 |
+
)])
|
229 |
+
|
230 |
+
fig.update_layout(
|
231 |
+
title=f"Salary Breakdown for ₹{salary:,.0f}",
|
232 |
+
font=dict(size=14),
|
233 |
+
showlegend=True,
|
234 |
+
height=400
|
235 |
+
)
|
236 |
+
|
237 |
+
return fig
|
238 |
+
|
239 |
+
def create_expense_analysis_chart(expenses: Dict[str, float]):
|
240 |
+
"""Create a bar chart for expense analysis"""
|
241 |
+
categories = list(expenses.keys())
|
242 |
+
amounts = list(expenses.values())
|
243 |
+
|
244 |
+
fig = go.Figure([go.Bar(
|
245 |
+
x=categories,
|
246 |
+
y=amounts,
|
247 |
+
marker_color='#667eea',
|
248 |
+
text=[f"₹{amount:,.0f}" for amount in amounts],
|
249 |
+
textposition='auto'
|
250 |
+
)])
|
251 |
+
|
252 |
+
fig.update_layout(
|
253 |
+
title="Monthly Expense Breakdown",
|
254 |
+
xaxis_title="Categories",
|
255 |
+
yaxis_title="Amount (₹)",
|
256 |
+
font=dict(size=12),
|
257 |
+
height=400
|
258 |
+
)
|
259 |
+
|
260 |
+
return fig
|
261 |
+
|
262 |
+
def process_financial_query(
|
263 |
+
salary: float,
|
264 |
+
rent: float,
|
265 |
+
food: float,
|
266 |
+
transport: float,
|
267 |
+
utilities: float,
|
268 |
+
entertainment: float,
|
269 |
+
other: float,
|
270 |
+
savings_goal: str,
|
271 |
+
user_question: str
|
272 |
+
) -> tuple:
|
273 |
+
"""Process user's financial query and return advice with visualizations"""
|
274 |
+
|
275 |
+
# Calculate totals
|
276 |
+
total_expenses = rent + food + transport + utilities + entertainment + other
|
277 |
+
remaining_salary = salary - total_expenses
|
278 |
+
|
279 |
+
# Create expense dictionary
|
280 |
+
expenses = {
|
281 |
+
"Rent": rent,
|
282 |
+
"Food": food,
|
283 |
+
"Transport": transport,
|
284 |
+
"Utilities": utilities,
|
285 |
+
"Entertainment": entertainment,
|
286 |
+
"Other": other
|
287 |
+
}
|
288 |
+
|
289 |
+
# Get salary breakdown using 50/30/20 rule
|
290 |
+
breakdown = mcp_server.salary_breakdown(salary, expenses)
|
291 |
+
needs = breakdown["breakdown"]["needs"]
|
292 |
+
wants = breakdown["breakdown"]["wants"]
|
293 |
+
savings = breakdown["breakdown"]["savings"]
|
294 |
+
|
295 |
+
# Create charts
|
296 |
+
salary_chart = create_salary_breakdown_chart(salary, needs, wants, savings)
|
297 |
+
expense_chart = create_expense_analysis_chart(expenses)
|
298 |
+
|
299 |
+
# Prepare context for Modal backend
|
300 |
+
context = {
|
301 |
+
"salary": salary,
|
302 |
+
"expenses": expenses,
|
303 |
+
"total_expenses": total_expenses,
|
304 |
+
"remaining_salary": remaining_salary,
|
305 |
+
"savings_goal": savings_goal,
|
306 |
+
"breakdown": breakdown
|
307 |
+
}
|
308 |
+
|
309 |
+
# Get AI advice
|
310 |
+
if user_question.strip():
|
311 |
+
advice = call_modal_backend(user_question, context)
|
312 |
+
else:
|
313 |
+
advice = call_modal_backend(f"Analyze my finances: Salary ₹{salary}, Total expenses ₹{total_expenses}", context)
|
314 |
+
|
315 |
+
# Create summary
|
316 |
+
summary = f"""
|
317 |
+
## 💰 Financial Summary
|
318 |
+
|
319 |
+
**Monthly Salary:** ₹{salary:,.0f}
|
320 |
+
**Total Expenses:** ₹{total_expenses:,.0f}
|
321 |
+
**Remaining Amount:** ₹{remaining_salary:,.0f}
|
322 |
+
|
323 |
+
### 📊 Recommended Allocation (50/30/20 Rule)
|
324 |
+
- **Needs (50%):** ₹{needs:,.0f}
|
325 |
+
- **Wants (30%):** ₹{wants:,.0f}
|
326 |
+
- **Savings (20%):** ₹{savings:,.0f}
|
327 |
+
|
328 |
+
### 🎯 Status
|
329 |
+
{'✅ Good job! You have money left over.' if remaining_salary > 0 else '⚠️ You are overspending. Consider reducing expenses.'}
|
330 |
+
"""
|
331 |
+
|
332 |
+
return salary_chart, expense_chart, summary, advice
|
333 |
+
|
334 |
+
def handle_quick_question(question: str, salary: float = 50000) -> str:
|
335 |
+
"""Handle pre-defined quick questions"""
|
336 |
+
context = {"salary": salary}
|
337 |
+
return call_modal_backend(question, context)
|
338 |
+
|
339 |
+
# Create Gradio interface
|
340 |
+
def create_moneymate_app():
|
341 |
+
with gr.Blocks(css=CUSTOM_CSS, title="MoneyMate - Your Financial Assistant") as app:
|
342 |
+
|
343 |
+
# Header
|
344 |
+
gr.HTML("""
|
345 |
+
<div class="main-header">
|
346 |
+
<h1>💰 MoneyMate</h1>
|
347 |
+
<p>Your Personal Financial Assistant for Smart Money Management</p>
|
348 |
+
<div class="modal-branding">⚡ Powered by Modal Labs</div>
|
349 |
+
</div>
|
350 |
+
""")
|
351 |
+
|
352 |
+
with gr.Row():
|
353 |
+
with gr.Column(scale=1):
|
354 |
+
gr.HTML('<div class="money-card">')
|
355 |
+
gr.Markdown("### 💼 Your Financial Details")
|
356 |
+
|
357 |
+
salary_input = gr.Number(
|
358 |
+
label="Monthly Salary (₹)",
|
359 |
+
value=50000,
|
360 |
+
minimum=0,
|
361 |
+
step=1000
|
362 |
+
)
|
363 |
+
|
364 |
+
gr.Markdown("#### Monthly Expenses")
|
365 |
+
rent_input = gr.Number(label="Rent (₹)", value=15000, minimum=0)
|
366 |
+
food_input = gr.Number(label="Food (₹)", value=8000, minimum=0)
|
367 |
+
transport_input = gr.Number(label="Transport (₹)", value=3000, minimum=0)
|
368 |
+
utilities_input = gr.Number(label="Utilities (₹)", value=2000, minimum=0)
|
369 |
+
entertainment_input = gr.Number(label="Entertainment (₹)", value=4000, minimum=0)
|
370 |
+
other_input = gr.Number(label="Other Expenses (₹)", value=3000, minimum=0)
|
371 |
+
|
372 |
+
savings_goal_input = gr.Textbox(
|
373 |
+
label="Savings Goal",
|
374 |
+
placeholder="e.g., Emergency fund, Vacation, House down payment",
|
375 |
+
value="Emergency fund"
|
376 |
+
)
|
377 |
+
|
378 |
+
user_question_input = gr.Textbox(
|
379 |
+
label="Ask MoneyMate",
|
380 |
+
placeholder="e.g., How should I invest my savings? What's the best way to save for a house?",
|
381 |
+
lines=3
|
382 |
+
)
|
383 |
+
|
384 |
+
analyze_btn = gr.Button("Analyze My Finances 📊", variant="primary", size="large")
|
385 |
+
gr.HTML('</div>')
|
386 |
+
|
387 |
+
# Quick action buttons
|
388 |
+
gr.HTML('<div class="money-card">')
|
389 |
+
gr.Markdown("### 🚀 Quick Questions")
|
390 |
+
|
391 |
+
with gr.Row():
|
392 |
+
quick_btn1 = gr.Button("💡 Investment Tips", size="small")
|
393 |
+
quick_btn2 = gr.Button("🏠 Save for House", size="small")
|
394 |
+
|
395 |
+
with gr.Row():
|
396 |
+
quick_btn3 = gr.Button("✈️ Plan Vacation", size="small")
|
397 |
+
quick_btn4 = gr.Button("🚗 Buy a Car", size="small")
|
398 |
+
|
399 |
+
gr.HTML('</div>')
|
400 |
+
|
401 |
+
with gr.Column(scale=2):
|
402 |
+
gr.HTML('<div class="money-card">')
|
403 |
+
|
404 |
+
# Output components
|
405 |
+
with gr.Tab("📊 Salary Breakdown"):
|
406 |
+
salary_chart_output = gr.Plot()
|
407 |
+
|
408 |
+
with gr.Tab("💸 Expense Analysis"):
|
409 |
+
expense_chart_output = gr.Plot()
|
410 |
+
|
411 |
+
with gr.Tab("📋 Summary"):
|
412 |
+
summary_output = gr.Markdown()
|
413 |
+
|
414 |
+
with gr.Tab("🤖 AI Advice"):
|
415 |
+
advice_output = gr.Markdown(value="Click 'Analyze My Finances' to get personalized advice!")
|
416 |
+
|
417 |
+
gr.HTML('</div>')
|
418 |
+
|
419 |
+
# Event handlers
|
420 |
+
analyze_btn.click(
|
421 |
+
fn=process_financial_query,
|
422 |
+
inputs=[
|
423 |
+
salary_input, rent_input, food_input, transport_input,
|
424 |
+
utilities_input, entertainment_input, other_input,
|
425 |
+
savings_goal_input, user_question_input
|
426 |
+
],
|
427 |
+
outputs=[salary_chart_output, expense_chart_output, summary_output, advice_output]
|
428 |
+
)
|
429 |
+
|
430 |
+
# Quick question handlers
|
431 |
+
quick_btn1.click(
|
432 |
+
fn=lambda s: handle_quick_question("What are the best investment options for a beginner in India?", s),
|
433 |
+
inputs=[salary_input],
|
434 |
+
outputs=[advice_output]
|
435 |
+
)
|
436 |
+
|
437 |
+
quick_btn2.click(
|
438 |
+
fn=lambda s: handle_quick_question("How should I save for buying a house in India?", s),
|
439 |
+
inputs=[salary_input],
|
440 |
+
outputs=[advice_output]
|
441 |
+
)
|
442 |
+
|
443 |
+
quick_btn3.click(
|
444 |
+
fn=lambda s: handle_quick_question("What's the best way to save for a vacation?", s),
|
445 |
+
inputs=[salary_input],
|
446 |
+
outputs=[advice_output]
|
447 |
+
)
|
448 |
+
|
449 |
+
quick_btn4.click(
|
450 |
+
fn=lambda s: handle_quick_question("How should I plan to buy a car with my salary?", s),
|
451 |
+
inputs=[salary_input],
|
452 |
+
outputs=[advice_output]
|
453 |
+
)
|
454 |
+
|
455 |
+
# Footer
|
456 |
+
gr.HTML("""
|
457 |
+
<div style="text-align: center; margin-top: 2rem; color: white;">
|
458 |
+
<p>Made with ❤️ for Agents & MCP Hackathon 2025</p>
|
459 |
+
<p>🏆 Track 1 — MCP Tool / Server</p>
|
460 |
+
</div>
|
461 |
+
""")
|
462 |
+
|
463 |
+
return app
|
464 |
+
|
465 |
+
# FastAPI wrapper for MCP compatibility
|
466 |
+
app_fastapi = FastAPI()
|
467 |
+
|
468 |
+
# Create and mount Gradio app
|
469 |
+
gradio_app = create_moneymate_app()
|
470 |
+
|
471 |
+
# MCP endpoints
|
472 |
+
@app_fastapi.post("/mcp/tools")
|
473 |
+
async def list_tools():
|
474 |
+
"""List available MCP tools"""
|
475 |
+
return {
|
476 |
+
"tools": [
|
477 |
+
{
|
478 |
+
"name": "salary_breakdown",
|
479 |
+
"description": "Break down salary using 50/30/20 rule",
|
480 |
+
"inputSchema": {
|
481 |
+
"type": "object",
|
482 |
+
"properties": {
|
483 |
+
"salary": {"type": "number"},
|
484 |
+
"expenses": {"type": "object"}
|
485 |
+
}
|
486 |
+
}
|
487 |
+
},
|
488 |
+
{
|
489 |
+
"name": "investment_advice",
|
490 |
+
"description": "Get investment advice for Indian market",
|
491 |
+
"inputSchema": {
|
492 |
+
"type": "object",
|
493 |
+
"properties": {
|
494 |
+
"age": {"type": "integer"},
|
495 |
+
"salary": {"type": "number"},
|
496 |
+
"risk_appetite": {"type": "string"}
|
497 |
+
}
|
498 |
+
}
|
499 |
+
}
|
500 |
+
]
|
501 |
+
}
|
502 |
+
|
503 |
+
@app_fastapi.post("/mcp/call_tool")
|
504 |
+
async def call_tool(request: dict):
|
505 |
+
"""Call MCP tool"""
|
506 |
+
tool_name = request.get("name")
|
507 |
+
arguments = request.get("arguments", {})
|
508 |
+
|
509 |
+
if tool_name in mcp_server.tools:
|
510 |
+
result = mcp_server.tools[tool_name](**arguments)
|
511 |
+
return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
|
512 |
+
else:
|
513 |
+
return {"error": f"Tool {tool_name} not found"}
|
514 |
+
|
515 |
+
# Mount Gradio app
|
516 |
+
gr.mount_gradio_app(app_fastapi, gradio_app, path="/")
|
517 |
+
|
518 |
+
if __name__ == "__main__":
|
519 |
+
gradio_app.launch(
|
520 |
+
server_name="0.0.0.0",
|
521 |
+
server_port=7860,
|
522 |
+
share=True
|
523 |
+
)
|
demo_inputs.json
ADDED
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"mcp_server_info": {
|
3 |
+
"name": "MoneyMate",
|
4 |
+
"version": "1.0.0",
|
5 |
+
"description": "AI-powered financial assistant for young Indian professionals",
|
6 |
+
"server_url": "https://kaustubhme0--moneymate-backend-fastapi-app.modal.run",
|
7 |
+
"gradio_app_url": "https://huggingface.co/spaces/[YOUR_USERNAME]/moneymate",
|
8 |
+
"mcp_compatibility": true
|
9 |
+
},
|
10 |
+
"sample_queries": [
|
11 |
+
{
|
12 |
+
"id": "salary_breakdown",
|
13 |
+
"category": "Salary Planning",
|
14 |
+
"user_input": "I earn ₹75,000 per month. How should I allocate my salary?",
|
15 |
+
"context": {
|
16 |
+
"salary": 75000,
|
17 |
+
"expenses": {
|
18 |
+
"rent": 25000,
|
19 |
+
"food": 8000,
|
20 |
+
"transport": 5000,
|
21 |
+
"utilities": 3000,
|
22 |
+
"entertainment": 7000
|
23 |
+
},
|
24 |
+
"total_expenses": 48000,
|
25 |
+
"remaining_salary": 27000,
|
26 |
+
"savings_goal": "Emergency fund and investments"
|
27 |
+
},
|
28 |
+
"expected_response_type": "financial_advice",
|
29 |
+
"description": "Basic salary allocation using 50/30/20 rule"
|
30 |
+
},
|
31 |
+
{
|
32 |
+
"id": "first_job_planning",
|
33 |
+
"category": "Career Starter",
|
34 |
+
"user_input": "I just got my first job at ₹45,000. What should I do with my money?",
|
35 |
+
"context": {
|
36 |
+
"salary": 45000,
|
37 |
+
"expenses": {
|
38 |
+
"rent": 15000,
|
39 |
+
"food": 6000,
|
40 |
+
"transport": 3000,
|
41 |
+
"utilities": 2000,
|
42 |
+
"personal": 3000
|
43 |
+
},
|
44 |
+
"total_expenses": 29000,
|
45 |
+
"remaining_salary": 16000,
|
46 |
+
"savings_goal": "Build foundation for financial future"
|
47 |
+
},
|
48 |
+
"expected_response_type": "financial_advice",
|
49 |
+
"description": "First job financial planning guidance"
|
50 |
+
},
|
51 |
+
{
|
52 |
+
"id": "investment_planning",
|
53 |
+
"category": "Investment Advice",
|
54 |
+
"user_input": "I have ₹20,000 left after expenses. How should I invest this money?",
|
55 |
+
"context": {
|
56 |
+
"salary": 60000,
|
57 |
+
"expenses": {
|
58 |
+
"rent": 20000,
|
59 |
+
"food": 7000,
|
60 |
+
"transport": 4000,
|
61 |
+
"utilities": 2500,
|
62 |
+
"entertainment": 6500
|
63 |
+
},
|
64 |
+
"total_expenses": 40000,
|
65 |
+
"remaining_salary": 20000,
|
66 |
+
"savings_goal": "Long-term wealth building"
|
67 |
+
},
|
68 |
+
"expected_response_type": "financial_advice",
|
69 |
+
"description": "Investment strategy for surplus income"
|
70 |
+
},
|
71 |
+
{
|
72 |
+
"id": "emergency_fund",
|
73 |
+
"category": "Emergency Planning",
|
74 |
+
"user_input": "How much should I keep as emergency fund and where should I keep it?",
|
75 |
+
"context": {
|
76 |
+
"salary": 80000,
|
77 |
+
"expenses": {
|
78 |
+
"rent": 30000,
|
79 |
+
"food": 10000,
|
80 |
+
"transport": 6000,
|
81 |
+
"utilities": 4000,
|
82 |
+
"entertainment": 8000,
|
83 |
+
"miscellaneous": 5000
|
84 |
+
},
|
85 |
+
"total_expenses": 63000,
|
86 |
+
"remaining_salary": 17000,
|
87 |
+
"savings_goal": "Build emergency fund"
|
88 |
+
},
|
89 |
+
"expected_response_type": "financial_advice",
|
90 |
+
"description": "Emergency fund planning and placement"
|
91 |
+
},
|
92 |
+
{
|
93 |
+
"id": "goal_based_saving",
|
94 |
+
"category": "Goal Planning",
|
95 |
+
"user_input": "I want to buy a car worth ₹8 lakhs in 2 years. How much should I save monthly?",
|
96 |
+
"context": {
|
97 |
+
"salary": 90000,
|
98 |
+
"expenses": {
|
99 |
+
"rent": 25000,
|
100 |
+
"food": 12000,
|
101 |
+
"transport": 8000,
|
102 |
+
"utilities": 4000,
|
103 |
+
"entertainment": 10000,
|
104 |
+
"miscellaneous": 6000
|
105 |
+
},
|
106 |
+
"total_expenses": 65000,
|
107 |
+
"remaining_salary": 25000,
|
108 |
+
"savings_goal": "Car purchase in 2 years - ₹8 lakhs"
|
109 |
+
},
|
110 |
+
"expected_response_type": "financial_advice",
|
111 |
+
"description": "Goal-based savings calculation"
|
112 |
+
},
|
113 |
+
{
|
114 |
+
"id": "tax_planning",
|
115 |
+
"category": "Tax Optimization",
|
116 |
+
"user_input": "I need to save tax under Section 80C. What are my best options?",
|
117 |
+
"context": {
|
118 |
+
"salary": 120000,
|
119 |
+
"expenses": {
|
120 |
+
"rent": 35000,
|
121 |
+
"food": 15000,
|
122 |
+
"transport": 8000,
|
123 |
+
"utilities": 5000,
|
124 |
+
"entertainment": 12000,
|
125 |
+
"miscellaneous": 8000
|
126 |
+
},
|
127 |
+
"total_expenses": 83000,
|
128 |
+
"remaining_salary": 37000,
|
129 |
+
"savings_goal": "Tax saving and wealth building"
|
130 |
+
},
|
131 |
+
"expected_response_type": "financial_advice",
|
132 |
+
"description": "Tax saving strategies under Section 80C"
|
133 |
+
},
|
134 |
+
{
|
135 |
+
"id": "expense_optimization",
|
136 |
+
"category": "Expense Management",
|
137 |
+
"user_input": "My expenses are too high. How can I reduce them without affecting my lifestyle?",
|
138 |
+
"context": {
|
139 |
+
"salary": 70000,
|
140 |
+
"expenses": {
|
141 |
+
"rent": 28000,
|
142 |
+
"food": 12000,
|
143 |
+
"transport": 8000,
|
144 |
+
"utilities": 4000,
|
145 |
+
"entertainment": 15000,
|
146 |
+
"shopping": 8000,
|
147 |
+
"miscellaneous": 5000
|
148 |
+
},
|
149 |
+
"total_expenses": 80000,
|
150 |
+
"remaining_salary": -10000,
|
151 |
+
"savings_goal": "Reduce expenses and start saving"
|
152 |
+
},
|
153 |
+
"expected_response_type": "financial_advice",
|
154 |
+
"description": "Expense optimization without lifestyle compromise"
|
155 |
+
},
|
156 |
+
{
|
157 |
+
"id": "retirement_planning",
|
158 |
+
"category": "Long-term Planning",
|
159 |
+
"user_input": "I'm 25 years old. When should I start planning for retirement?",
|
160 |
+
"context": {
|
161 |
+
"salary": 65000,
|
162 |
+
"expenses": {
|
163 |
+
"rent": 22000,
|
164 |
+
"food": 8000,
|
165 |
+
"transport": 5000,
|
166 |
+
"utilities": 3000,
|
167 |
+
"entertainment": 8000,
|
168 |
+
"miscellaneous": 4000
|
169 |
+
},
|
170 |
+
"total_expenses": 50000,
|
171 |
+
"remaining_salary": 15000,
|
172 |
+
"savings_goal": "Early retirement planning"
|
173 |
+
},
|
174 |
+
"expected_response_type": "financial_advice",
|
175 |
+
"description": "Early career retirement planning"
|
176 |
+
}
|
177 |
+
],
|
178 |
+
"mcp_integration_examples": [
|
179 |
+
{
|
180 |
+
"client": "Cursor IDE",
|
181 |
+
"query": "Help me plan my ₹80,000 salary allocation",
|
182 |
+
"mcp_request": {
|
183 |
+
"method": "POST",
|
184 |
+
"endpoint": "/financial_advice",
|
185 |
+
"payload": {
|
186 |
+
"user_input": "Help me plan my ₹80,000 salary allocation",
|
187 |
+
"context": {
|
188 |
+
"salary": 80000,
|
189 |
+
"expenses": {},
|
190 |
+
"savings_goal": "General financial planning"
|
191 |
+
}
|
192 |
+
}
|
193 |
+
}
|
194 |
+
},
|
195 |
+
{
|
196 |
+
"client": "Claude Desktop",
|
197 |
+
"query": "Calculate SIP returns for ₹10,000 monthly investment",
|
198 |
+
"mcp_request": {
|
199 |
+
"method": "POST",
|
200 |
+
"endpoint": "/calculate_returns",
|
201 |
+
"payload": {
|
202 |
+
"monthly_investment": 10000,
|
203 |
+
"annual_return_rate": 12,
|
204 |
+
"years": 10
|
205 |
+
}
|
206 |
+
}
|
207 |
+
}
|
208 |
+
],
|
209 |
+
"gradio_interface_examples": [
|
210 |
+
{
|
211 |
+
"salary_input": 75000,
|
212 |
+
"expenses_input": "rent 25000, food 8000, transport 5000, utilities 3000, entertainment 7000",
|
213 |
+
"question_input": "How should I allocate my remaining ₹27,000?",
|
214 |
+
"expected_output": "Markdown formatted advice with pie chart showing allocation"
|
215 |
+
},
|
216 |
+
{
|
217 |
+
"salary_input": 45000,
|
218 |
+
"expenses_input": "rent 15000, food 6000, transport 3000, utilities 2000, personal 3000",
|
219 |
+
"question_input": "What's the best way to start investing?",
|
220 |
+
"expected_output": "Beginner-friendly investment advice with risk assessment"
|
221 |
+
}
|
222 |
+
],
|
223 |
+
"testing_scenarios": [
|
224 |
+
{
|
225 |
+
"name": "High Expenses",
|
226 |
+
"salary": 50000,
|
227 |
+
"expenses": "rent 30000, food 10000, transport 5000, entertainment 8000",
|
228 |
+
"question": "My expenses exceed my salary. What should I do?",
|
229 |
+
"expected_behavior": "Expense optimization advice and practical solutions"
|
230 |
+
},
|
231 |
+
{
|
232 |
+
"name": "High Earner",
|
233 |
+
"salary": 200000,
|
234 |
+
"expenses": "rent 40000, food 15000, transport 10000, utilities 5000, entertainment 20000",
|
235 |
+
"question": "How should I invest my surplus income?",
|
236 |
+
"expected_behavior": "Advanced investment strategies and tax planning"
|
237 |
+
},
|
238 |
+
{
|
239 |
+
"name": "Conservative Investor",
|
240 |
+
"salary": 60000,
|
241 |
+
"expenses": "rent 20000, food 8000, transport 4000, utilities 3000, entertainment 5000",
|
242 |
+
"question": "I'm afraid of stock market risks. What are safe investment options?",
|
243 |
+
"expected_behavior": "Conservative investment options with risk explanation"
|
244 |
+
}
|
245 |
+
]
|
246 |
+
}
|
modal_app.py
ADDED
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import modal
|
2 |
+
import json
|
3 |
+
from typing import Dict, Any, Optional
|
4 |
+
from datetime import datetime
|
5 |
+
import os
|
6 |
+
import urllib.request
|
7 |
+
import urllib.parse
|
8 |
+
|
9 |
+
# Create Modal app
|
10 |
+
app = modal.App("moneymate-backend")
|
11 |
+
|
12 |
+
# Create image with required dependencies
|
13 |
+
image = modal.Image.debian_slim(python_version="3.11").pip_install([
|
14 |
+
"fastapi",
|
15 |
+
"pydantic"
|
16 |
+
])
|
17 |
+
|
18 |
+
# vLLM server configuration
|
19 |
+
# TODO: Replace with your actual vLLM server URL after deployment
|
20 |
+
VLLM_SERVER_URL = "https://kaustubhme0--example-vllm-openai-compatible-serve.modal.run"
|
21 |
+
|
22 |
+
# This API key should match the API_KEY in your vLLM inference script (paste.txt)
|
23 |
+
# It's YOUR custom key, not from any external service
|
24 |
+
VLLM_API_KEY = "super-secret-key"
|
25 |
+
|
26 |
+
# Financial advisor system prompt
|
27 |
+
SYSTEM_PROMPT = """You are MoneyMate, a friendly and knowledgeable financial advisor specifically designed for young Indian professionals who are new to managing their salaries and finances.
|
28 |
+
|
29 |
+
PERSONALITY & TONE:
|
30 |
+
- Warm, approachable, and encouraging
|
31 |
+
- Use simple, jargon-free language
|
32 |
+
- Be supportive and non-judgmental
|
33 |
+
- Add appropriate emojis to make advice engaging
|
34 |
+
- Safety-first approach - always emphasize careful consideration
|
35 |
+
|
36 |
+
EXPERTISE AREAS:
|
37 |
+
- Salary allocation and budgeting
|
38 |
+
- Indian investment options (SIP, ELSS, PPF, NPS, etc.)
|
39 |
+
- Tax-saving strategies under Indian tax law
|
40 |
+
- Emergency fund planning
|
41 |
+
- Goal-based financial planning
|
42 |
+
- Expense optimization
|
43 |
+
|
44 |
+
RESPONSE FORMAT:
|
45 |
+
- Use markdown formatting for better readability
|
46 |
+
- Include practical examples with Indian Rupee amounts
|
47 |
+
- Provide step-by-step actionable advice
|
48 |
+
- Always include safety disclaimers for investment advice
|
49 |
+
- Suggest starting small and learning gradually
|
50 |
+
|
51 |
+
CULTURAL CONTEXT:
|
52 |
+
- Understand Indian financial instruments and regulations
|
53 |
+
- Consider family financial responsibilities common in India
|
54 |
+
- Be aware of Indian tax implications (80C, LTCG, etc.)
|
55 |
+
- Reference Indian banks, mutual fund companies, and financial platforms
|
56 |
+
|
57 |
+
SAFETY GUIDELINES:
|
58 |
+
- Never guarantee returns or specific outcomes
|
59 |
+
- Always recommend consulting financial advisors for major decisions
|
60 |
+
- Emphasize the importance of emergency funds before investing
|
61 |
+
- Warn about risks associated with investments
|
62 |
+
- Promote financial literacy and gradual learning
|
63 |
+
|
64 |
+
Remember: You're helping someone who may be handling their first salary. Be patient, educational, and encouraging while prioritizing their financial safety and security.
|
65 |
+
"""
|
66 |
+
|
67 |
+
def call_vllm_api(messages: list, max_tokens: int = 1500, temperature: float = 0.7) -> str:
|
68 |
+
"""Call the vLLM server with OpenAI-compatible API"""
|
69 |
+
|
70 |
+
headers = {
|
71 |
+
"Authorization": f"Bearer {VLLM_API_KEY}",
|
72 |
+
"Content-Type": "application/json",
|
73 |
+
}
|
74 |
+
|
75 |
+
payload = {
|
76 |
+
"messages": messages,
|
77 |
+
"model": "neuralmagic/Meta-Llama-3.1-8B-Instruct-quantized.w4a16",
|
78 |
+
"max_tokens": max_tokens,
|
79 |
+
"temperature": temperature
|
80 |
+
}
|
81 |
+
|
82 |
+
try:
|
83 |
+
req = urllib.request.Request(
|
84 |
+
f"{VLLM_SERVER_URL}/v1/chat/completions",
|
85 |
+
data=json.dumps(payload).encode("utf-8"),
|
86 |
+
headers=headers,
|
87 |
+
method="POST",
|
88 |
+
)
|
89 |
+
|
90 |
+
# Increased timeout to 180 seconds (3 minutes) for LLM responses
|
91 |
+
with urllib.request.urlopen(req, timeout=180) as response:
|
92 |
+
if response.getcode() == 200:
|
93 |
+
result = json.loads(response.read().decode())
|
94 |
+
return result["choices"][0]["message"]["content"]
|
95 |
+
else:
|
96 |
+
response_text = response.read().decode()
|
97 |
+
raise Exception(f"API call failed with status {response.getcode()}: {response_text}")
|
98 |
+
|
99 |
+
except urllib.error.URLError as e:
|
100 |
+
if "timeout" in str(e).lower():
|
101 |
+
raise Exception("Request timed out. The vLLM server might be taking too long to respond or might be cold starting.")
|
102 |
+
else:
|
103 |
+
raise Exception(f"Network error calling vLLM API: {str(e)}")
|
104 |
+
except Exception as e:
|
105 |
+
raise Exception(f"Error calling vLLM API: {str(e)}")
|
106 |
+
|
107 |
+
@app.function(
|
108 |
+
image=image,
|
109 |
+
timeout=300, # Increased to 5 minutes
|
110 |
+
memory=1024
|
111 |
+
)
|
112 |
+
def get_financial_advice(user_input: str, context: Dict[str, Any] = None) -> str:
|
113 |
+
"""Generate financial advice using local vLLM server"""
|
114 |
+
|
115 |
+
try:
|
116 |
+
# Prepare context information
|
117 |
+
context_str = ""
|
118 |
+
if context:
|
119 |
+
salary = context.get("salary", 0)
|
120 |
+
expenses = context.get("expenses", {})
|
121 |
+
total_expenses = context.get("total_expenses", 0)
|
122 |
+
remaining_salary = context.get("remaining_salary", 0)
|
123 |
+
savings_goal = context.get("savings_goal", "")
|
124 |
+
|
125 |
+
context_str = f"""
|
126 |
+
CURRENT FINANCIAL SITUATION:
|
127 |
+
- Monthly Salary: ₹{salary:,.0f}
|
128 |
+
- Total Monthly Expenses: ₹{total_expenses:,.0f}
|
129 |
+
- Remaining Amount: ₹{remaining_salary:,.0f}
|
130 |
+
- Savings Goal: {savings_goal}
|
131 |
+
|
132 |
+
EXPENSE BREAKDOWN:
|
133 |
+
"""
|
134 |
+
|
135 |
+
for category, amount in expenses.items():
|
136 |
+
if amount > 0:
|
137 |
+
context_str += f"- {category}: ₹{amount:,.0f}\n"
|
138 |
+
|
139 |
+
# Create the full prompt
|
140 |
+
user_prompt = f"""
|
141 |
+
{context_str}
|
142 |
+
|
143 |
+
USER QUESTION: {user_input}
|
144 |
+
|
145 |
+
Please provide personalized financial advice based on this information. Focus on:
|
146 |
+
1. Specific recommendations for their situation
|
147 |
+
2. Practical next steps they can take
|
148 |
+
3. Indian financial instruments and strategies
|
149 |
+
4. Risk management and safety
|
150 |
+
5. Educational insights to help them learn
|
151 |
+
"""
|
152 |
+
|
153 |
+
# Prepare messages for vLLM
|
154 |
+
messages = [
|
155 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
156 |
+
{"role": "user", "content": user_prompt}
|
157 |
+
]
|
158 |
+
|
159 |
+
# Call vLLM API
|
160 |
+
advice = call_vllm_api(messages, max_tokens=1500, temperature=0.7)
|
161 |
+
|
162 |
+
# Add timestamp and additional context
|
163 |
+
current_time = datetime.now().strftime("%B %d, %Y at %I:%M %p")
|
164 |
+
|
165 |
+
formatted_advice = f"""
|
166 |
+
{advice}
|
167 |
+
|
168 |
+
---
|
169 |
+
|
170 |
+
💡 **MoneyMate Tips:**
|
171 |
+
- Start small and gradually increase your investments
|
172 |
+
- Always maintain 6-12 months of expenses as emergency fund
|
173 |
+
- Review and adjust your financial plan quarterly
|
174 |
+
- Consider consulting a certified financial planner for major decisions
|
175 |
+
|
176 |
+
*Advice generated on {current_time}*
|
177 |
+
"""
|
178 |
+
|
179 |
+
return formatted_advice.strip()
|
180 |
+
|
181 |
+
except Exception as e:
|
182 |
+
error_msg = f"""
|
183 |
+
## 😔 Sorry, I'm having trouble right now
|
184 |
+
|
185 |
+
I encountered an error while processing your request: {str(e)}
|
186 |
+
|
187 |
+
**Here are some general tips while I'm unavailable:**
|
188 |
+
|
189 |
+
### 💰 Basic Financial Rules for Young Professionals:
|
190 |
+
- **50/30/20 Rule**: Allocate 50% for needs, 30% for wants, 20% for savings
|
191 |
+
- **Emergency Fund**: Build 6-12 months of expenses before investing
|
192 |
+
- **Start Small**: Begin with SIP of ₹1000-2000 monthly in diversified funds
|
193 |
+
- **Tax Planning**: Use ELSS funds to save tax under Section 80C
|
194 |
+
|
195 |
+
### 🏦 Safe Investment Options for Beginners:
|
196 |
+
- **SIP in Index Funds**: Low cost, diversified exposure
|
197 |
+
- **PPF**: 15-year lock-in, tax-free returns
|
198 |
+
- **ELSS Funds**: Tax saving with 3-year lock-in
|
199 |
+
- **Liquid Funds**: For emergency fund parking
|
200 |
+
|
201 |
+
Please try again in a few moments, or check your connection to the vLLM server.
|
202 |
+
"""
|
203 |
+
|
204 |
+
return error_msg
|
205 |
+
|
206 |
+
@app.function(
|
207 |
+
image=image,
|
208 |
+
timeout=30
|
209 |
+
)
|
210 |
+
def analyze_expenses(expenses: Dict[str, float], salary: float) -> Dict[str, Any]:
|
211 |
+
"""Analyze expense patterns and provide optimization suggestions"""
|
212 |
+
|
213 |
+
total_expenses = sum(expenses.values())
|
214 |
+
expense_ratio = (total_expenses / salary) * 100 if salary > 0 else 0
|
215 |
+
|
216 |
+
analysis = {
|
217 |
+
"total_expenses": total_expenses,
|
218 |
+
"expense_ratio": expense_ratio,
|
219 |
+
"status": "healthy" if expense_ratio < 70 else "concerning",
|
220 |
+
"recommendations": []
|
221 |
+
}
|
222 |
+
|
223 |
+
# Analyze each category
|
224 |
+
for category, amount in expenses.items():
|
225 |
+
category_ratio = (amount / salary) * 100 if salary > 0 else 0
|
226 |
+
|
227 |
+
# Category-specific recommendations
|
228 |
+
if category.lower() == "rent" and category_ratio > 40:
|
229 |
+
analysis["recommendations"].append(f"🏠 Rent is high ({category_ratio:.1f}% of salary). Consider relocating or finding roommates.")
|
230 |
+
elif category.lower() == "food" and category_ratio > 20:
|
231 |
+
analysis["recommendations"].append(f"🍽️ Food expenses are high ({category_ratio:.1f}% of salary). Try cooking at home more often.")
|
232 |
+
elif category.lower() == "entertainment" and category_ratio > 15:
|
233 |
+
analysis["recommendations"].append(f"🎬 Entertainment expenses are high ({category_ratio:.1f}% of salary). Set a monthly budget and stick to it.")
|
234 |
+
|
235 |
+
return analysis
|
236 |
+
|
237 |
+
@app.function(
|
238 |
+
image=image,
|
239 |
+
timeout=30
|
240 |
+
)
|
241 |
+
def calculate_investment_returns(
|
242 |
+
monthly_investment: float,
|
243 |
+
annual_return_rate: float,
|
244 |
+
years: int
|
245 |
+
) -> Dict[str, Any]:
|
246 |
+
"""Calculate SIP returns with compound interest"""
|
247 |
+
|
248 |
+
monthly_rate = annual_return_rate / 12 / 100
|
249 |
+
total_months = years * 12
|
250 |
+
|
251 |
+
# SIP future value formula
|
252 |
+
if monthly_rate > 0:
|
253 |
+
future_value = monthly_investment * (((1 + monthly_rate) ** total_months - 1) / monthly_rate) * (1 + monthly_rate)
|
254 |
+
else:
|
255 |
+
future_value = monthly_investment * total_months
|
256 |
+
|
257 |
+
total_invested = monthly_investment * total_months
|
258 |
+
returns = future_value - total_invested
|
259 |
+
|
260 |
+
return {
|
261 |
+
"future_value": round(future_value, 2),
|
262 |
+
"total_invested": round(total_invested, 2),
|
263 |
+
"returns": round(returns, 2),
|
264 |
+
"return_percentage": round((returns / total_invested) * 100, 2) if total_invested > 0 else 0
|
265 |
+
}
|
266 |
+
|
267 |
+
@app.function(
|
268 |
+
image=image,
|
269 |
+
timeout=60
|
270 |
+
)
|
271 |
+
def health_check_vllm() -> Dict[str, Any]:
|
272 |
+
"""Check if vLLM server is healthy"""
|
273 |
+
try:
|
274 |
+
with urllib.request.urlopen(f"{VLLM_SERVER_URL}/health", timeout=30) as response:
|
275 |
+
if response.getcode() == 200:
|
276 |
+
return {"vllm_status": "healthy", "server_url": VLLM_SERVER_URL}
|
277 |
+
else:
|
278 |
+
return {"vllm_status": "unhealthy", "server_url": VLLM_SERVER_URL, "status_code": response.getcode()}
|
279 |
+
except Exception as e:
|
280 |
+
return {"vllm_status": "error", "error": str(e), "server_url": VLLM_SERVER_URL}
|
281 |
+
|
282 |
+
# FastAPI app for HTTP endpoints
|
283 |
+
@app.function(image=image)
|
284 |
+
@modal.asgi_app()
|
285 |
+
def fastapi_app():
|
286 |
+
from fastapi import FastAPI, HTTPException
|
287 |
+
from pydantic import BaseModel
|
288 |
+
from typing import Optional
|
289 |
+
|
290 |
+
api = FastAPI(title="MoneyMate Backend API with vLLM")
|
291 |
+
|
292 |
+
class FinancialAdviceRequest(BaseModel):
|
293 |
+
user_input: str
|
294 |
+
context: Optional[Dict[str, Any]] = None
|
295 |
+
|
296 |
+
class ExpenseAnalysisRequest(BaseModel):
|
297 |
+
expenses: Dict[str, float]
|
298 |
+
salary: float
|
299 |
+
|
300 |
+
class InvestmentCalculationRequest(BaseModel):
|
301 |
+
monthly_investment: float
|
302 |
+
annual_return_rate: float
|
303 |
+
years: int
|
304 |
+
|
305 |
+
@api.post("/financial_advice")
|
306 |
+
async def get_advice(request: FinancialAdviceRequest):
|
307 |
+
"""Get AI-powered financial advice using vLLM"""
|
308 |
+
try:
|
309 |
+
advice = get_financial_advice.remote(request.user_input, request.context)
|
310 |
+
return {"advice": advice}
|
311 |
+
except Exception as e:
|
312 |
+
raise HTTPException(status_code=500, detail=str(e))
|
313 |
+
|
314 |
+
@api.post("/analyze_expenses")
|
315 |
+
async def analyze_user_expenses(request: ExpenseAnalysisRequest):
|
316 |
+
"""Analyze user's expense patterns"""
|
317 |
+
try:
|
318 |
+
analysis = analyze_expenses.remote(request.expenses, request.salary)
|
319 |
+
return {"analysis": analysis}
|
320 |
+
except Exception as e:
|
321 |
+
raise HTTPException(status_code=500, detail=str(e))
|
322 |
+
|
323 |
+
@api.post("/calculate_returns")
|
324 |
+
async def calculate_returns(request: InvestmentCalculationRequest):
|
325 |
+
"""Calculate SIP investment returns"""
|
326 |
+
try:
|
327 |
+
returns = calculate_investment_returns.remote(
|
328 |
+
request.monthly_investment,
|
329 |
+
request.annual_return_rate,
|
330 |
+
request.years
|
331 |
+
)
|
332 |
+
return {"returns": returns}
|
333 |
+
except Exception as e:
|
334 |
+
raise HTTPException(status_code=500, detail=str(e))
|
335 |
+
|
336 |
+
@api.get("/health")
|
337 |
+
async def health_check():
|
338 |
+
"""Health check endpoint including vLLM server status"""
|
339 |
+
vllm_health = health_check_vllm.remote()
|
340 |
+
return {
|
341 |
+
"status": "healthy",
|
342 |
+
"service": "MoneyMate Backend with vLLM",
|
343 |
+
"vllm_server": vllm_health
|
344 |
+
}
|
345 |
+
|
346 |
+
@api.get("/")
|
347 |
+
async def root():
|
348 |
+
"""Root endpoint with service information"""
|
349 |
+
return {
|
350 |
+
"service": "MoneyMate Backend API with vLLM",
|
351 |
+
"version": "2.0.0",
|
352 |
+
"description": "AI-powered financial advice for young Indian professionals using local vLLM server",
|
353 |
+
"vllm_server": VLLM_SERVER_URL,
|
354 |
+
"endpoints": [
|
355 |
+
"/financial_advice",
|
356 |
+
"/analyze_expenses",
|
357 |
+
"/calculate_returns",
|
358 |
+
"/health"
|
359 |
+
]
|
360 |
+
}
|
361 |
+
|
362 |
+
return api
|
363 |
+
|
364 |
+
# Test function to verify vLLM integration
|
365 |
+
@app.local_entrypoint()
|
366 |
+
def test_vllm_integration():
|
367 |
+
"""Test the vLLM integration with a sample financial question"""
|
368 |
+
|
369 |
+
print(f"Testing vLLM server at: {VLLM_SERVER_URL}")
|
370 |
+
print("Testing vLLM server health...")
|
371 |
+
|
372 |
+
health = health_check_vllm.remote()
|
373 |
+
print(f"vLLM Health: {health}")
|
374 |
+
|
375 |
+
if health.get("vllm_status") == "healthy":
|
376 |
+
print("\nTesting simple financial advice generation...")
|
377 |
+
|
378 |
+
# Start with a very simple question first
|
379 |
+
try:
|
380 |
+
advice = get_financial_advice.remote("What is SIP?", None)
|
381 |
+
print("Generated Advice:")
|
382 |
+
print("=" * 50)
|
383 |
+
print(advice)
|
384 |
+
except Exception as e:
|
385 |
+
print(f"Error during simple test: {e}")
|
386 |
+
|
387 |
+
print("\nTesting with context...")
|
388 |
+
sample_context = {
|
389 |
+
"salary": 50000,
|
390 |
+
"expenses": {"rent": 15000, "food": 8000, "transport": 3000},
|
391 |
+
"total_expenses": 26000,
|
392 |
+
"remaining_salary": 24000,
|
393 |
+
"savings_goal": "Emergency fund and investments"
|
394 |
+
}
|
395 |
+
|
396 |
+
try:
|
397 |
+
advice = get_financial_advice.remote(
|
398 |
+
"How should I invest my remaining salary?",
|
399 |
+
sample_context
|
400 |
+
)
|
401 |
+
print("Generated Advice with Context:")
|
402 |
+
print("=" * 50)
|
403 |
+
print(advice)
|
404 |
+
except Exception as e:
|
405 |
+
print(f"Error during context test: {e}")
|
406 |
+
else:
|
407 |
+
print("vLLM server is not healthy. Please check:")
|
408 |
+
print("1. Is your vLLM server deployed and running?")
|
409 |
+
print("2. Is the VLLM_SERVER_URL correct?")
|
410 |
+
print("3. Is the API_KEY matching?")
|
411 |
+
print(f"Current URL: {VLLM_SERVER_URL}")
|
412 |
+
print(f"Current API Key: {VLLM_API_KEY}")
|
requirements.txt
ADDED
File without changes
|
system_prompt.txt
ADDED
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
You are MoneyMate, a friendly and knowledgeable financial advisor specifically designed for young Indian professionals who are new to managing their salaries and finances. You are part of a web application that helps users make smart financial decisions through interactive guidance and personalized advice.
|
2 |
+
|
3 |
+
## YOUR PERSONALITY & COMMUNICATION STYLE
|
4 |
+
|
5 |
+
**Tone & Approach:**
|
6 |
+
- Warm, approachable, and encouraging - like a helpful friend who knows about money
|
7 |
+
- Use simple, jargon-free language that anyone can understand
|
8 |
+
- Be supportive and non-judgmental about past financial decisions
|
9 |
+
- Add appropriate emojis (💰🏦📊💡🎯) to make advice engaging and friendly
|
10 |
+
- Safety-first approach - always emphasize careful consideration and gradual learning
|
11 |
+
- Encourage users to start small and build confidence over time
|
12 |
+
|
13 |
+
**Communication Guidelines:**
|
14 |
+
- Keep responses conversational but informative
|
15 |
+
- Use practical examples with Indian Rupee amounts
|
16 |
+
- Break down complex concepts into simple steps
|
17 |
+
- Always acknowledge the user's current situation positively
|
18 |
+
- Provide specific, actionable advice rather than generic statements
|
19 |
+
|
20 |
+
## YOUR EXPERTISE AREAS
|
21 |
+
|
22 |
+
**Core Financial Topics:**
|
23 |
+
1. **Salary Management:** Budgeting, 50/30/20 rule, expense tracking
|
24 |
+
2. **Indian Investments:** SIP, mutual funds, ELSS, PPF, NPS, FDs, bonds
|
25 |
+
3. **Tax Planning:** Section 80C, LTCG, tax-saving instruments
|
26 |
+
4. **Emergency Planning:** Emergency fund sizing and placement
|
27 |
+
5. **Goal-Based Planning:** House, car, vacation, marriage, children's education
|
28 |
+
6. **Expense Optimization:** Reducing costs without compromising lifestyle
|
29 |
+
7. **Insurance:** Health, term life, and other protection needs
|
30 |
+
8. **Retirement Planning:** Early career retirement planning strategies
|
31 |
+
|
32 |
+
**Indian Financial Context:**
|
33 |
+
- Deep knowledge of Indian financial instruments and regulations
|
34 |
+
- Understanding of Indian banking system (SBI, HDFC, ICICI, etc.)
|
35 |
+
- Familiarity with Indian mutual fund companies (SBI, HDFC, Axis, etc.)
|
36 |
+
- Knowledge of Indian tax laws and implications
|
37 |
+
- Awareness of Indian cultural factors affecting financial decisions
|
38 |
+
- Understanding of family financial responsibilities common in India
|
39 |
+
|
40 |
+
## RESPONSE FORMATTING GUIDELINES
|
41 |
+
|
42 |
+
**Structure Your Responses:**
|
43 |
+
1. **Acknowledgment:** Briefly acknowledge their situation
|
44 |
+
2. **Main Advice:** Provide core recommendations
|
45 |
+
3. **Action Steps:** Give specific next steps they can take
|
46 |
+
4. **Educational Insight:** Explain why this advice matters
|
47 |
+
5. **Safety Note:** Include appropriate disclaimers
|
48 |
+
|
49 |
+
**Markdown Formatting:**
|
50 |
+
- Use headers (##, ###) to organize long responses
|
51 |
+
- Use bullet points for lists and action items
|
52 |
+
- Use **bold** for important concepts or amounts
|
53 |
+
- Use *italics* for emphasis or explanations
|
54 |
+
- Include relevant emojis to make content engaging
|
55 |
+
|
56 |
+
**Example Response Structure:**
|
57 |
+
```
|
58 |
+
## 💰 Great question about [topic]!
|
59 |
+
|
60 |
+
Based on your salary of ₹X and expenses of ₹Y, here's what I recommend:
|
61 |
+
|
62 |
+
### 🎯 Immediate Action Steps:
|
63 |
+
- Step 1 with specific amount/action
|
64 |
+
- Step 2 with timeline
|
65 |
+
- Step 3 with expected outcome
|
66 |
+
|
67 |
+
### 📚 Why This Matters:
|
68 |
+
Brief educational explanation of the concept
|
69 |
+
|
70 |
+
### ⚠️ Important Considerations:
|
71 |
+
- Safety notes or risks to consider
|
72 |
+
- When to seek professional help
|
73 |
+
```
|
74 |
+
|
75 |
+
## SAFETY GUIDELINES & DISCLAIMERS
|
76 |
+
|
77 |
+
**Investment Advice Safety:**
|
78 |
+
- Never guarantee specific returns or outcomes
|
79 |
+
- Always mention that "past performance doesn't guarantee future results"
|
80 |
+
- Emphasize the importance of diversification
|
81 |
+
- Recommend starting with small amounts to learn
|
82 |
+
- Suggest consulting certified financial planners for large decisions
|
83 |
+
- Always mention investment risks appropriately
|
84 |
+
|
85 |
+
**Standard Disclaimers to Include:**
|
86 |
+
- "This is general advice based on your situation. Consider consulting a certified financial planner for personalized strategies."
|
87 |
+
- "Start with small amounts and gradually increase as you become more comfortable."
|
88 |
+
- "Remember to maintain your emergency fund before making any investments."
|
89 |
+
- "Investment markets can be volatile - only invest money you won't need for several years."
|
90 |
+
|
91 |
+
## SPECIFIC ADVICE FRAMEWORKS
|
92 |
+
|
93 |
+
**For Salary Breakdown Questions:**
|
94 |
+
1. Acknowledge their current allocation
|
95 |
+
2. Explain the 50/30/20 rule in Indian context
|
96 |
+
3. Provide specific rupee amounts for their salary
|
97 |
+
4. Suggest gradual adjustments, not dramatic changes
|
98 |
+
5. Prioritize emergency fund before investments
|
99 |
+
|
100 |
+
**For Investment Questions:**
|
101 |
+
1. Assess their risk tolerance and timeline
|
102 |
+
2. Start with basics: emergency fund, then SIP
|
103 |
+
3. Explain Indian investment options clearly
|
104 |
+
4. Provide expected return ranges (realistic, not optimistic)
|
105 |
+
5. Emphasize diversification and regular investing
|
106 |
+
|
107 |
+
**For Goal-Based Planning:**
|
108 |
+
1. Help them quantify the goal amount
|
109 |
+
2. Calculate timeline and monthly savings needed
|
110 |
+
3. Suggest appropriate investment vehicles
|
111 |
+
4. Break down the plan into phases
|
112 |
+
5. Provide motivation and milestone tracking
|
113 |
+
|
114 |
+
**For Expense Optimization:**
|
115 |
+
1. Analyze their expense ratios
|
116 |
+
2. Identify 2-3 key areas for improvement
|
117 |
+
3. Provide practical, implementable tips
|
118 |
+
4. Suggest apps or tools that can help
|
119 |
+
5. Emphasize lifestyle balance, not deprivation
|
120 |
+
|
121 |
+
## CULTURAL SENSITIVITY & INDIAN CONTEXT
|
122 |
+
|
123 |
+
**Family Considerations:**
|
124 |
+
- Acknowledge family financial responsibilities
|
125 |
+
- Understand joint family financial dynamics
|
126 |
+
- Respect cultural values around money and saving
|
127 |
+
- Consider social and cultural pressures around spending
|
128 |
+
|
129 |
+
**Indian Market Specifics:**
|
130 |
+
- Reference familiar brands and institutions
|
131 |
+
- Use relevant examples (EMI, festivals, bonus cycles)
|
132 |
+
- Understand salary structures (basic, HRA, allowances)
|
133 |
+
- Know about common employee benefits (PF, gratuity, health insurance)
|
134 |
+
|
135 |
+
**Regional Awareness:**
|
136 |
+
- Acknowledge cost of living differences across Indian cities
|
137 |
+
- Understand metro vs tier-2/3 city financial challenges
|
138 |
+
- Be aware of regional investment preferences and options
|
139 |
+
|
140 |
+
## CONVERSATION FLOW GUIDELINES
|
141 |
+
|
142 |
+
**For First-Time Users:**
|
143 |
+
- Welcome them warmly to financial planning
|
144 |
+
- Reassure them that starting is the hardest part
|
145 |
+
- Focus on building basic financial habits first
|
146 |
+
- Encourage them to ask questions without hesitation
|
147 |
+
|
148 |
+
**For Follow-Up Questions:**
|
149 |
+
- Reference their previous financial situation if provided
|
150 |
+
- Build upon earlier advice given
|
151 |
+
- Show progress and encourage continued learning
|
152 |
+
- Adapt advice based on their growing knowledge
|
153 |
+
|
154 |
+
**For Complex Situations:**
|
155 |
+
- Break down into smaller, manageable parts
|
156 |
+
- Prioritize the most important actions first
|
157 |
+
- Acknowledge when situations require professional help
|
158 |
+
- Provide resources for additional learning
|
159 |
+
|
160 |
+
## RESPONSE LENGTH & DETAIL
|
161 |
+
|
162 |
+
**For Simple Questions:** 2-3 paragraphs with key points
|
163 |
+
**For Complex Topics:** Detailed breakdown with sections and action steps
|
164 |
+
**For Calculations:** Show the math clearly with examples
|
165 |
+
**For Explanations:** Use analogies and real-world examples
|
166 |
+
|
167 |
+
Remember: Your goal is to make financial planning accessible, less intimidating, and culturally relevant for young Indian professionals. You're not just giving advice - you're building their financial confidence and literacy for life.
|
utils.py
ADDED
@@ -0,0 +1,385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Utility functions for MoneyMate financial calculations and validations
|
3 |
+
"""
|
4 |
+
|
5 |
+
from typing import Dict, List, Tuple, Any
|
6 |
+
import re
|
7 |
+
from datetime import datetime, timedelta
|
8 |
+
|
9 |
+
def validate_salary(salary: float) -> Tuple[bool, str]:
|
10 |
+
"""Validate salary input"""
|
11 |
+
if salary <= 0:
|
12 |
+
return False, "Salary must be greater than 0"
|
13 |
+
if salary < 10000:
|
14 |
+
return False, "Salary seems too low. Please enter monthly salary in Indian Rupees"
|
15 |
+
if salary > 10000000: # 1 crore
|
16 |
+
return False, "Salary seems too high. Please verify the amount"
|
17 |
+
return True, "Valid salary"
|
18 |
+
|
19 |
+
def validate_expenses(expenses: Dict[str, float], salary: float) -> Tuple[bool, str, List[str]]:
|
20 |
+
"""Validate expense inputs"""
|
21 |
+
warnings = []
|
22 |
+
total_expenses = sum(expenses.values())
|
23 |
+
|
24 |
+
# Check if total expenses exceed salary
|
25 |
+
if total_expenses > salary:
|
26 |
+
return False, f"Total expenses (₹{total_expenses:,.0f}) exceed salary (₹{salary:,.0f})", warnings
|
27 |
+
|
28 |
+
# Check individual expense categories
|
29 |
+
for category, amount in expenses.items():
|
30 |
+
if amount < 0:
|
31 |
+
return False, f"{category} cannot be negative", warnings
|
32 |
+
|
33 |
+
# Category-specific validations
|
34 |
+
category_lower = category.lower()
|
35 |
+
percentage = (amount / salary) * 100 if salary > 0 else 0
|
36 |
+
|
37 |
+
if category_lower == "rent" and percentage > 50:
|
38 |
+
warnings.append(f"Rent is {percentage:.1f}% of salary - consider reducing housing costs")
|
39 |
+
elif category_lower == "food" and percentage > 30:
|
40 |
+
warnings.append(f"Food expenses are {percentage:.1f}% of salary - try cooking at home more")
|
41 |
+
elif category_lower == "entertainment" and percentage > 20:
|
42 |
+
warnings.append(f"Entertainment is {percentage:.1f}% of salary - consider budgeting")
|
43 |
+
|
44 |
+
return True, "Valid expenses", warnings
|
45 |
+
|
46 |
+
def calculate_50_30_20_breakdown(salary: float) -> Dict[str, float]:
|
47 |
+
"""Calculate 50/30/20 rule breakdown"""
|
48 |
+
return {
|
49 |
+
"needs": salary * 0.50,
|
50 |
+
"wants": salary * 0.30,
|
51 |
+
"savings": salary * 0.20
|
52 |
+
}
|
53 |
+
|
54 |
+
def calculate_emergency_fund_target(monthly_expenses: float, months: int = 6) -> Dict[str, Any]:
|
55 |
+
"""Calculate emergency fund target"""
|
56 |
+
target_amount = monthly_expenses * months
|
57 |
+
|
58 |
+
return {
|
59 |
+
"target_amount": target_amount,
|
60 |
+
"months_coverage": months,
|
61 |
+
"monthly_expenses": monthly_expenses,
|
62 |
+
"recommendation": f"Build emergency fund of ₹{target_amount:,.0f} ({months} months of expenses)"
|
63 |
+
}
|
64 |
+
|
65 |
+
def calculate_sip_returns(
|
66 |
+
monthly_amount: float,
|
67 |
+
annual_return: float,
|
68 |
+
years: int,
|
69 |
+
inflation_rate: float = 6.0
|
70 |
+
) -> Dict[str, Any]:
|
71 |
+
"""Calculate SIP returns with inflation adjustment"""
|
72 |
+
|
73 |
+
months = years * 12
|
74 |
+
monthly_return = annual_return / 12 / 100
|
75 |
+
monthly_inflation = inflation_rate / 12 / 100
|
76 |
+
|
77 |
+
# Future value calculation
|
78 |
+
if monthly_return > 0:
|
79 |
+
future_value = monthly_amount * (((1 + monthly_return) ** months - 1) / monthly_return) * (1 + monthly_return)
|
80 |
+
else:
|
81 |
+
future_value = monthly_amount * months
|
82 |
+
|
83 |
+
total_invested = monthly_amount * months
|
84 |
+
absolute_returns = future_value - total_invested
|
85 |
+
|
86 |
+
# Inflation-adjusted value
|
87 |
+
inflation_adjusted_value = future_value / ((1 + monthly_inflation) ** months)
|
88 |
+
real_returns = inflation_adjusted_value - total_invested
|
89 |
+
|
90 |
+
return {
|
91 |
+
"future_value": round(future_value, 2),
|
92 |
+
"total_invested": round(total_invested, 2),
|
93 |
+
"absolute_returns": round(absolute_returns, 2),
|
94 |
+
"inflation_adjusted_value": round(inflation_adjusted_value, 2),
|
95 |
+
"real_returns": round(real_returns, 2),
|
96 |
+
"return_percentage": round((absolute_returns / total_invested) * 100, 2) if total_invested > 0 else 0,
|
97 |
+
"real_return_percentage": round((real_returns / total_invested) * 100, 2) if total_invested > 0 else 0,
|
98 |
+
"years": years,
|
99 |
+
"monthly_investment": monthly_amount
|
100 |
+
}
|
101 |
+
|
102 |
+
def get_tax_saving_instruments() -> List[Dict[str, Any]]:
|
103 |
+
"""Get list of tax-saving instruments under Section 80C"""
|
104 |
+
return [
|
105 |
+
{
|
106 |
+
"name": "ELSS Mutual Funds",
|
107 |
+
"lock_in": "3 years",
|
108 |
+
"expected_return": "10-12%",
|
109 |
+
"risk": "Medium to High",
|
110 |
+
"liquidity": "Low (until lock-in)",
|
111 |
+
"description": "Equity-linked savings schemes with tax benefits"
|
112 |
+
},
|
113 |
+
{
|
114 |
+
"name": "PPF (Public Provident Fund)",
|
115 |
+
"lock_in": "15 years",
|
116 |
+
"expected_return": "7-8%",
|
117 |
+
"risk": "Very Low",
|
118 |
+
"liquidity": "Very Low",
|
119 |
+
"description": "Government-backed long-term savings with tax-free returns"
|
120 |
+
},
|
121 |
+
{
|
122 |
+
"name": "NSC (National Savings Certificate)",
|
123 |
+
"lock_in": "5 years",
|
124 |
+
"expected_return": "6-7%",
|
125 |
+
"risk": "Very Low",
|
126 |
+
"liquidity": "Low",
|
127 |
+
"description": "Government savings certificate with fixed returns"
|
128 |
+
},
|
129 |
+
{
|
130 |
+
"name": "Life Insurance Premium",
|
131 |
+
"lock_in": "Varies",
|
132 |
+
"expected_return": "4-6%",
|
133 |
+
"risk": "Low",
|
134 |
+
"liquidity": "Low to Medium",
|
135 |
+
"description": "Term/whole life insurance premiums qualify for deduction"
|
136 |
+
},
|
137 |
+
{
|
138 |
+
"name": "Home Loan Principal",
|
139 |
+
"lock_in": "Loan tenure",
|
140 |
+
"expected_return": "Property appreciation",
|
141 |
+
"risk": "Medium",
|
142 |
+
"liquidity": "Low",
|
143 |
+
"description": "Principal repayment of home loan qualifies for deduction"
|
144 |
+
}
|
145 |
+
]
|
146 |
+
|
147 |
+
def get_investment_allocation_by_age(age: int) -> Dict[str, Any]:
|
148 |
+
"""Get recommended investment allocation based on age"""
|
149 |
+
|
150 |
+
if age < 25:
|
151 |
+
equity_percentage = 80
|
152 |
+
debt_percentage = 20
|
153 |
+
risk_profile = "High"
|
154 |
+
elif age < 35:
|
155 |
+
equity_percentage = 70
|
156 |
+
debt_percentage = 30
|
157 |
+
risk_profile = "Medium to High"
|
158 |
+
elif age < 45:
|
159 |
+
equity_percentage = 60
|
160 |
+
debt_percentage = 40
|
161 |
+
risk_profile = "Medium"
|
162 |
+
elif age < 55:
|
163 |
+
equity_percentage = 50
|
164 |
+
debt_percentage = 50
|
165 |
+
risk_profile = "Medium to Low"
|
166 |
+
else:
|
167 |
+
equity_percentage = 30
|
168 |
+
debt_percentage = 70
|
169 |
+
risk_profile = "Low"
|
170 |
+
|
171 |
+
return {
|
172 |
+
"age": age,
|
173 |
+
"equity_percentage": equity_percentage,
|
174 |
+
"debt_percentage": debt_percentage,
|
175 |
+
"risk_profile": risk_profile,
|
176 |
+
"rationale": f"At age {age}, you can take {risk_profile.lower()} risk with {equity_percentage}% equity allocation"
|
177 |
+
}
|
178 |
+
|
179 |
+
def calculate_goal_based_savings(
|
180 |
+
goal_amount: float,
|
181 |
+
current_savings: float,
|
182 |
+
timeline_years: float,
|
183 |
+
expected_return: float = 12.0
|
184 |
+
) -> Dict[str, Any]:
|
185 |
+
"""Calculate monthly savings needed for a financial goal"""
|
186 |
+
|
187 |
+
timeline_months = int(timeline_years * 12)
|
188 |
+
monthly_return = expected_return / 12 / 100
|
189 |
+
|
190 |
+
# Amount needed to invest
|
191 |
+
amount_needed = goal_amount - current_savings
|
192 |
+
|
193 |
+
if amount_needed <= 0:
|
194 |
+
return {
|
195 |
+
"goal_amount": goal_amount,
|
196 |
+
"current_savings": current_savings,
|
197 |
+
"amount_needed": 0,
|
198 |
+
"monthly_required": 0,
|
199 |
+
"timeline_months": timeline_months,
|
200 |
+
"status": "Goal already achieved!",
|
201 |
+
"recommendation": "Consider setting a higher goal or investing surplus for other objectives"
|
202 |
+
}
|
203 |
+
|
204 |
+
# Calculate monthly SIP required
|
205 |
+
if monthly_return > 0:
|
206 |
+
# PMT formula for SIP calculation
|
207 |
+
monthly_sip = amount_needed * monthly_return / (((1 + monthly_return) ** timeline_months - 1) * (1 + monthly_return))
|
208 |
+
else:
|
209 |
+
monthly_sip = amount_needed / timeline_months
|
210 |
+
|
211 |
+
# Determine feasibility
|
212 |
+
if monthly_sip < 1000:
|
213 |
+
feasibility = "Very Easy"
|
214 |
+
elif monthly_sip < 5000:
|
215 |
+
feasibility = "Easy"
|
216 |
+
elif monthly_sip < 15000:
|
217 |
+
feasibility = "Moderate"
|
218 |
+
elif monthly_sip < 30000:
|
219 |
+
feasibility = "Challenging"
|
220 |
+
else:
|
221 |
+
feasibility = "Very Challenging"
|
222 |
+
|
223 |
+
return {
|
224 |
+
"goal_amount": goal_amount,
|
225 |
+
"current_savings": current_savings,
|
226 |
+
"amount_needed": amount_needed,
|
227 |
+
"monthly_required": round(monthly_sip, 2),
|
228 |
+
"timeline_months": timeline_months,
|
229 |
+
"timeline_years": timeline_years,
|
230 |
+
"expected_return": expected_return,
|
231 |
+
"feasibility": feasibility,
|
232 |
+
"recommendation": get_goal_recommendation(monthly_sip, feasibility)
|
233 |
+
}
|
234 |
+
|
235 |
+
def get_goal_recommendation(monthly_sip: float, feasibility: str) -> str:
|
236 |
+
"""Get recommendation based on goal feasibility"""
|
237 |
+
if feasibility == "Very Easy":
|
238 |
+
return f"Great! ₹{monthly_sip:,.0f} per month is very manageable. Consider increasing the goal or timeline."
|
239 |
+
elif feasibility == "Easy":
|
240 |
+
return f"₹{monthly_sip:,.0f} per month should be comfortable. Start a SIP immediately."
|
241 |
+
elif feasibility == "Moderate":
|
242 |
+
return f"₹{monthly_sip:,.0f} per month requires discipline. Create a budget and automate your investments."
|
243 |
+
elif feasibility == "Challenging":
|
244 |
+
return f"₹{monthly_sip:,.0f} per month is ambitious. Consider extending timeline or reducing goal amount."
|
245 |
+
else:
|
246 |
+
return f"₹{monthly_sip:,.0f} per month seems unrealistic. Reassess your goal amount or timeline."
|
247 |
+
|
248 |
+
def get_expense_optimization_tips(expenses: Dict[str, float], salary: float) -> List[str]:
|
249 |
+
"""Get personalized expense optimization tips"""
|
250 |
+
tips = []
|
251 |
+
|
252 |
+
for category, amount in expenses.items():
|
253 |
+
percentage = (amount / salary) * 100 if salary > 0 else 0
|
254 |
+
category_lower = category.lower()
|
255 |
+
|
256 |
+
if category_lower == "rent" and percentage > 30:
|
257 |
+
tips.extend([
|
258 |
+
"🏠 Consider sharing accommodation or moving to a less expensive area",
|
259 |
+
"🏠 Negotiate rent with landlord or look for better deals",
|
260 |
+
"🏠 Explore PG/hostel options if you're single"
|
261 |
+
])
|
262 |
+
|
263 |
+
elif category_lower == "food" and percentage > 20:
|
264 |
+
tips.extend([
|
265 |
+
"🍽️ Cook at home more often - it's healthier and cheaper",
|
266 |
+
"🍽️ Meal prep on weekends to avoid ordering food",
|
267 |
+
"🍽️ Use grocery apps for discounts and bulk buying"
|
268 |
+
])
|
269 |
+
|
270 |
+
elif category_lower == "transport" and percentage > 15:
|
271 |
+
tips.extend([
|
272 |
+
"🚗 Use public transport or carpool to save money",
|
273 |
+
"🚗 Consider a bike/scooter if you frequently use cabs",
|
274 |
+
"🚗 Work from home when possible to reduce commute costs"
|
275 |
+
])
|
276 |
+
|
277 |
+
elif category_lower == "entertainment" and percentage > 15:
|
278 |
+
tips.extend([
|
279 |
+
"🎬 Set a monthly entertainment budget and stick to it",
|
280 |
+
"🎬 Look for free/low-cost activities like parks, museums",
|
281 |
+
"🎬 Use streaming services instead of frequent movie outings"
|
282 |
+
])
|
283 |
+
|
284 |
+
# Add general tips
|
285 |
+
general_tips = [
|
286 |
+
"📱 Review and cancel unused subscriptions",
|
287 |
+
"💡 Use energy-efficient appliances to reduce utility bills",
|
288 |
+
"🛒 Make a shopping list and stick to it to avoid impulse purchases",
|
289 |
+
"💳 Use credit cards wisely - pay full amount to avoid interest",
|
290 |
+
"🏪 Compare prices before making major purchases"
|
291 |
+
]
|
292 |
+
|
293 |
+
tips.extend(general_tips[:3]) # Add 3 general tips
|
294 |
+
|
295 |
+
return list(set(tips)) # Remove duplicates
|
296 |
+
|
297 |
+
def format_currency(amount: float) -> str:
|
298 |
+
"""Format amount in Indian currency format"""
|
299 |
+
if amount >= 10000000: # 1 crore
|
300 |
+
return f"₹{amount/10000000:.1f} Cr"
|
301 |
+
elif amount >= 100000: # 1 lakh
|
302 |
+
return f"₹{amount/100000:.1f} L"
|
303 |
+
elif amount >= 1000: # 1 thousand
|
304 |
+
return f"₹{amount/1000:.1f} K"
|
305 |
+
else:
|
306 |
+
return f"₹{amount:,.0f}"
|
307 |
+
|
308 |
+
def get_financial_milestones_by_age() -> Dict[int, List[str]]:
|
309 |
+
"""Get financial milestones by age group"""
|
310 |
+
return {
|
311 |
+
25: [
|
312 |
+
"Build emergency fund of 3-6 months expenses",
|
313 |
+
"Start investing in equity mutual funds via SIP",
|
314 |
+
"Get health insurance coverage",
|
315 |
+
"Begin tax planning with ELSS/PPF"
|
316 |
+
],
|
317 |
+
30: [
|
318 |
+
"Emergency fund of 6-12 months expenses",
|
319 |
+
"Diversified investment portfolio worth 2-3x annual salary",
|
320 |
+
"Term life insurance coverage",
|
321 |
+
"Start planning for major goals (house, marriage)"
|
322 |
+
],
|
323 |
+
35: [
|
324 |
+
"Net worth of 5-7x annual salary",
|
325 |
+
"Own house or significant progress towards it",
|
326 |
+
"Children's education planning started",
|
327 |
+
"Retirement planning begun"
|
328 |
+
],
|
329 |
+
40: [
|
330 |
+
"Net worth of 8-10x annual salary",
|
331 |
+
"Debt-to-income ratio below 30%",
|
332 |
+
"Children's education fund secured",
|
333 |
+
"Aggressive retirement planning"
|
334 |
+
],
|
335 |
+
45: [
|
336 |
+
"Net worth of 12-15x annual salary",
|
337 |
+
"House loan paid off or nearing completion",
|
338 |
+
"Children's higher education funded",
|
339 |
+
"Retirement corpus building accelerated"
|
340 |
+
]
|
341 |
+
}
|
342 |
+
|
343 |
+
def calculate_retirement_corpus(
|
344 |
+
current_age: int,
|
345 |
+
retirement_age: int,
|
346 |
+
current_monthly_expenses: float,
|
347 |
+
inflation_rate: float = 6.0,
|
348 |
+
withdrawal_rate: float = 4.0
|
349 |
+
) -> Dict[str, Any]:
|
350 |
+
"""Calculate retirement corpus requirement"""
|
351 |
+
|
352 |
+
years_to_retirement = retirement_age - current_age
|
353 |
+
|
354 |
+
if years_to_retirement <= 0:
|
355 |
+
return {"error": "Retirement age should be greater than current age"}
|
356 |
+
|
357 |
+
# Future value of current expenses
|
358 |
+
future_monthly_expenses = current_monthly_expenses * ((1 + inflation_rate/100) ** years_to_retirement)
|
359 |
+
future_annual_expenses = future_monthly_expenses * 12
|
360 |
+
|
361 |
+
# Corpus required (using 4% rule)
|
362 |
+
corpus_required = future_annual_expenses / (withdrawal_rate / 100)
|
363 |
+
|
364 |
+
return {
|
365 |
+
"current_age": current_age,
|
366 |
+
"retirement_age": retirement_age,
|
367 |
+
"years_to_retirement": years_to_retirement,
|
368 |
+
"current_monthly_expenses": current_monthly_expenses,
|
369 |
+
"future_monthly_expenses": round(future_monthly_expenses, 2),
|
370 |
+
"corpus_required": round(corpus_required, 2),
|
371 |
+
"corpus_in_crores": round(corpus_required / 10000000, 2),
|
372 |
+
"monthly_sip_required": round(calculate_sip_for_target(corpus_required, years_to_retirement), 2)
|
373 |
+
}
|
374 |
+
|
375 |
+
def calculate_sip_for_target(target_amount: float, years: int, expected_return: float = 12.0) -> float:
|
376 |
+
"""Calculate monthly SIP required to reach target amount"""
|
377 |
+
months = years * 12
|
378 |
+
monthly_return = expected_return / 12 / 100
|
379 |
+
|
380 |
+
if monthly_return > 0:
|
381 |
+
monthly_sip = target_amount * monthly_return / (((1 + monthly_return) ** months - 1) * (1 + monthly_return))
|
382 |
+
else:
|
383 |
+
monthly_sip = target_amount / months
|
384 |
+
|
385 |
+
return monthly_sip
|
vllm_inference.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import modal
|
2 |
+
|
3 |
+
vllm_image = (
|
4 |
+
modal.Image.debian_slim(python_version="3.12")
|
5 |
+
.pip_install(
|
6 |
+
"vllm==0.7.2",
|
7 |
+
"huggingface_hub[hf_transfer]==0.26.2",
|
8 |
+
"flashinfer-python==0.2.0.post2", # pinning, very unstable
|
9 |
+
extra_index_url="https://flashinfer.ai/whl/cu124/torch2.5",
|
10 |
+
)
|
11 |
+
.env({"HF_HUB_ENABLE_HF_TRANSFER": "1"}) # faster model transfers
|
12 |
+
)
|
13 |
+
|
14 |
+
|
15 |
+
vllm_image = vllm_image.env({"VLLM_USE_V1": "1"})
|
16 |
+
|
17 |
+
|
18 |
+
MODELS_DIR = "/llamas"
|
19 |
+
MODEL_NAME = "neuralmagic/Meta-Llama-3.1-8B-Instruct-quantized.w4a16"
|
20 |
+
MODEL_REVISION = "a7c09948d9a632c2c840722f519672cd94af885d"
|
21 |
+
|
22 |
+
hf_cache_vol = modal.Volume.from_name("huggingface-cache", create_if_missing=True)
|
23 |
+
vllm_cache_vol = modal.Volume.from_name("vllm-cache", create_if_missing=True)
|
24 |
+
|
25 |
+
|
26 |
+
|
27 |
+
app = modal.App("example-vllm-openai-compatible")
|
28 |
+
|
29 |
+
N_GPU = 2 # tip: for best results, first upgrade to more powerful GPUs, and only then increase GPU count
|
30 |
+
API_KEY = "super-secret-key" # api key, for auth. for production use, replace with a modal.Secret
|
31 |
+
|
32 |
+
MINUTES = 60 # seconds
|
33 |
+
|
34 |
+
VLLM_PORT = 8000
|
35 |
+
|
36 |
+
|
37 |
+
@app.function(
|
38 |
+
image=vllm_image,
|
39 |
+
gpu=f"H100:{N_GPU}",
|
40 |
+
scaledown_window=15 * MINUTES, # how long should we stay up with no requests?
|
41 |
+
timeout=10 * MINUTES, # how long should we wait for container start?
|
42 |
+
volumes={
|
43 |
+
"/root/.cache/huggingface": hf_cache_vol,
|
44 |
+
"/root/.cache/vllm": vllm_cache_vol,
|
45 |
+
},
|
46 |
+
)
|
47 |
+
@modal.concurrent(
|
48 |
+
max_inputs=100
|
49 |
+
) # how many requests can one replica handle? tune carefully!
|
50 |
+
@modal.web_server(port=VLLM_PORT, startup_timeout=5 * MINUTES)
|
51 |
+
def serve():
|
52 |
+
import subprocess
|
53 |
+
|
54 |
+
cmd = [
|
55 |
+
"vllm",
|
56 |
+
"serve",
|
57 |
+
"--uvicorn-log-level=info",
|
58 |
+
MODEL_NAME,
|
59 |
+
"--revision",
|
60 |
+
MODEL_REVISION,
|
61 |
+
"--host",
|
62 |
+
"0.0.0.0",
|
63 |
+
"--port",
|
64 |
+
str(VLLM_PORT),
|
65 |
+
"--api-key",
|
66 |
+
API_KEY,
|
67 |
+
]
|
68 |
+
|
69 |
+
subprocess.Popen(" ".join(cmd), shell=True)
|