Rahul2298 commited on
Commit
b2303ae
·
verified ·
1 Parent(s): 62d68f0

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +114 -700
src/streamlit_app.py CHANGED
@@ -1,323 +1,59 @@
1
  import os
2
  import io
3
  import re
4
- import json
5
  from dataclasses import dataclass
6
  from typing import List, Dict, Optional
7
- from datetime import datetime, timedelta
8
  import pandas as pd
9
  import streamlit as st
10
 
11
- # Enhanced import handling with better error management
12
  try:
13
  from transformers import pipeline
14
  HF_AVAILABLE = True
15
- HF_PIPELINE = pipeline
16
- except ImportError:
17
- HF_AVAILABLE = False
18
- HF_PIPELINE = None
19
  except Exception:
20
  HF_AVAILABLE = False
21
- HF_PIPELINE = None
22
 
23
- st.set_page_config(page_title="Personal Finance Chatbot", page_icon="💰", layout="wide")
 
24
 
25
- # Initialize session state
26
  if "chat_history" not in st.session_state:
27
- st.session_state.chat_history = []
 
 
28
  if "provider_inited" not in st.session_state:
29
- st.session_state.provider_inited = False
30
  if "provider_name" not in st.session_state:
31
- st.session_state.provider_name = "enhanced_rule_based"
 
32
 
33
  class AIProvider:
34
  def __init__(self):
35
  self.name = "base"
36
-
37
  def generate(self, prompt: str, max_tokens: int = 512) -> str:
38
  raise NotImplementedError
39
 
40
- class EnhancedRuleBasedProvider(AIProvider):
41
- def __init__(self):
42
- super().__init__()
43
- self.name = "enhanced_rule_based"
44
-
45
- # Enhanced financial advice templates
46
- self.advice_templates = {
47
- "savings": {
48
- "emergency": "Build an emergency fund covering 3-6 months of expenses. Start with ₹1000/month and gradually increase.",
49
- "goal_based": "For specific goals, use SIP investments. For ₹10L goal in 5 years, invest ₹12,000 monthly in diversified mutual funds.",
50
- "tax_saving": "Utilize Section 80C (₹1.5L limit): PPF, ELSS, life insurance. Consider ULIP for dual benefits."
51
- },
52
- "investment": {
53
- "beginner": "Start with index funds (Nifty 50, Sensex). Low cost, diversified, suitable for beginners.",
54
- "moderate": "70% equity (diversified funds), 20% debt funds, 10% gold/REITs for balanced growth.",
55
- "aggressive": "80-90% equity across large-cap, mid-cap, small-cap funds. Higher risk, higher potential returns."
56
- },
57
- "tax": {
58
- "salaried": "Use 80C, 80D (health insurance), NPS for additional ₹50k deduction. Choose new/old regime wisely.",
59
- "professional": "Maintain books, claim business expenses, advance tax payments. Consider professional help."
60
- },
61
- "budget": {
62
- "50_30_20": "Follow 50-30-20 rule: 50% needs, 30% wants, 20% savings/investments.",
63
- "expense_tracking": "Track all expenses for 3 months. Identify spending patterns and unnecessary expenses."
64
- }
65
- }
66
-
67
- def generate(self, prompt: str, max_tokens: int = 512) -> str:
68
- prompt_lower = prompt.lower()
69
-
70
- # Enhanced intent detection and response generation
71
- if any(word in prompt_lower for word in ["emergency", "emergency fund"]):
72
- response = self._generate_emergency_fund_advice(prompt)
73
- elif any(word in prompt_lower for word in ["invest", "investment", "sip", "mutual fund"]):
74
- response = self._generate_investment_advice(prompt)
75
- elif any(word in prompt_lower for word in ["tax", "80c", "deduction"]):
76
- response = self._generate_tax_advice(prompt)
77
- elif any(word in prompt_lower for word in ["budget", "expense", "spending"]):
78
- response = self._generate_budget_advice(prompt)
79
- elif any(word in prompt_lower for word in ["goal", "target", "lakhs", "crores"]):
80
- response = self._generate_goal_based_advice(prompt)
81
- else:
82
- response = self._generate_general_advice(prompt)
83
-
84
- return response
85
-
86
- def _extract_amount_from_prompt(self, prompt: str) -> Optional[float]:
87
- """Extract monetary amounts from prompt"""
88
- amount_patterns = [
89
- r'₹(\d+(?:,\d+)*(?:\.\d+)?)\s*(?:lakh|lakhs|l)',
90
- r'₹(\d+(?:,\d+)*(?:\.\d+)?)\s*(?:crore|crores|cr)',
91
- r'₹(\d+(?:,\d+)*(?:\.\d+)?)',
92
- r'(\d+(?:,\d+)*(?:\.\d+)?)\s*(?:lakh|lakhs|l)',
93
- r'(\d+(?:,\d+)*(?:\.\d+)?)\s*(?:crore|crores|cr)'
94
- ]
95
-
96
- for pattern in amount_patterns:
97
- match = re.search(pattern, prompt.lower())
98
- if match:
99
- amount_str = match.group(1).replace(',', '')
100
- amount = float(amount_str)
101
- if 'lakh' in prompt.lower():
102
- amount *= 100000
103
- elif 'crore' in prompt.lower():
104
- amount *= 10000000
105
- return amount
106
- return None
107
-
108
- def _generate_emergency_fund_advice(self, prompt: str) -> str:
109
- return """
110
- ## 🚨 Emergency Fund Strategy
111
-
112
- **Quick Answer:** Build 3-6 months of expenses as emergency fund in high-yield savings account.
113
-
114
- **Why it matters:**
115
- - Protects against job loss, medical emergencies, unexpected expenses
116
- - Prevents debt accumulation during crises
117
- - Provides peace of mind and financial stability
118
-
119
- **Next Steps:**
120
- • Calculate monthly expenses (rent, utilities, groceries, EMIs)
121
- • Target: 6 months × monthly expenses for professionals, 3 months for students
122
- • Start with ₹500-1000/month, increase by 10% every 6 months
123
- • Keep in liquid funds or high-yield savings (avoid FDs for emergency funds)
124
- • Separate account to avoid spending temptation
125
-
126
- **Caution Notes:**
127
- - Don't invest emergency funds in equity/volatile instruments
128
- - Ensure easy access within 24-48 hours
129
- - Review and adjust annually based on expense changes
130
- """
131
-
132
- def _generate_investment_advice(self, prompt: str) -> str:
133
- amount = self._extract_amount_from_prompt(prompt)
134
- time_match = re.search(r'(\d+)\s*(?:year|years|yr)', prompt.lower())
135
- time_horizon = int(time_match.group(1)) if time_match else 5
136
-
137
- if amount:
138
- monthly_sip = amount / (time_horizon * 12) * 1.2 # Adding buffer for returns
139
- return f"""
140
- ## 📈 Investment Strategy for ₹{amount:,.0f} Goal
141
-
142
- **Quick Answer:** Invest ₹{monthly_sip:,.0f}/month via SIP for {time_horizon} years.
143
-
144
- **Why it matters:**
145
- - Compound growth over time maximizes returns
146
- - SIP reduces market timing risk through rupee cost averaging
147
- - Disciplined approach builds wealth systematically
148
-
149
- **Next Steps:**
150
- • Large Cap Funds: 40% (₹{monthly_sip*0.4:,.0f}/month) - Stable, established companies
151
- • Mid Cap Funds: 30% (₹{monthly_sip*0.3:,.0f}/month) - Higher growth potential
152
- • Index Funds: 20% (₹{monthly_sip*0.2:,.0f}/month) - Low cost, market returns
153
- • Debt Funds: 10% (₹{monthly_sip*0.1:,.0f}/month) - Stability, reduce volatility
154
- • Start SIP on same date monthly (preferably salary date)
155
- • Review portfolio every 6 months, rebalance annually
156
-
157
- **Caution Notes:**
158
- - Stay invested for full tenure, avoid panic selling
159
- - Expected returns: 10-12% annually (not guaranteed)
160
- - Market volatility is normal, focus on long-term goals
161
- """
162
-
163
- return """
164
- ## 📈 General Investment Principles
165
-
166
- **Quick Answer:** Start SIP in diversified equity funds, begin with ₹1000/month.
167
-
168
- **Asset Allocation by Age:**
169
- • 20s-30s: 80% equity, 20% debt (aggressive growth)
170
- • 30s-40s: 70% equity, 30% debt (balanced growth)
171
- • 40s+: 60% equity, 40% debt (conservative)
172
-
173
- **Investment Ladder:**
174
- 1. **Emergency Fund** (3-6 months expenses)
175
- 2. **Insurance** (term + health)
176
- 3. **Tax Saving** (ELSS under 80C)
177
- 4. **Goal-based SIPs** (house, education, retirement)
178
- 5. **Wealth Creation** (additional equity investments)
179
- """
180
-
181
- def _generate_tax_advice(self, prompt: str) -> str:
182
- return """
183
- ## 💰 Tax Optimization Strategy
184
-
185
- **Quick Answer:** Maximize deductions under 80C (₹1.5L), 80D (₹25k-₹50k), NPS (₹50k additional).
186
-
187
- **Major Tax Sections:**
188
- • **80C (₹1.5L limit):** PPF, ELSS, life insurance, principal repayment
189
- • **80D:** Health insurance premiums (₹25k self, ₹25k parents, ₹50k senior parents)
190
- • **80CCD(1B):** NPS additional ₹50k deduction
191
- • **24B:** Home loan interest up to ₹2L (self-occupied property)
192
-
193
- **Next Steps:**
194
- • Calculate tax liability under both regimes (old vs new)
195
- • PPF: ₹1.5L annually, 15-year lock-in, tax-free returns
196
- • ELSS: Market-linked, 3-year lock-in, potential for higher returns
197
- • Health insurance: Essential protection + tax benefit
198
- • Maintain investment proofs and Form 16
199
-
200
- **Caution Notes:**
201
- - Don't invest just for tax saving, consider returns too
202
- - New tax regime has lower rates but fewer deductions
203
- - Consult CA for complex situations or high income
204
- """
205
-
206
- def _generate_budget_advice(self, prompt: str) -> str:
207
- return """
208
- ## 📊 Smart Budgeting Strategy
209
-
210
- **Quick Answer:** Follow 50-30-20 rule and track every expense for 3 months.
211
-
212
- **50-30-20 Breakdown:**
213
- • **50% Needs:** Rent, groceries, utilities, EMIs, insurance
214
- • **30% Wants:** Dining out, entertainment, shopping, hobbies
215
- • **20% Savings:** Emergency fund, investments, goal-based savings
216
-
217
- **Expense Tracking Tools:**
218
- • Apps: Mint, ET Money, Walnut, Money Manager
219
- • Excel template with categories
220
- • Monthly expense review and optimization
221
-
222
- **Next Steps:**
223
- • List all income sources and amounts
224
- • Track expenses by category for 3 months
225
- • Identify spending patterns and leakages
226
- • Set category-wise budgets and stick to them
227
- • Automate savings on salary day
228
- • Review monthly, adjust quarterly
229
-
230
- **Caution Notes:**
231
- - Be realistic with budgets, allow some flexibility
232
- - Don't cut all enjoyment expenses
233
- - Emergency fund is priority before investments
234
- """
235
-
236
- def _generate_goal_based_advice(self, prompt: str) -> str:
237
- amount = self._extract_amount_from_prompt(prompt)
238
- if amount:
239
- # Calculate SIP amounts for different time horizons
240
- sip_5yr = amount / (5 * 12) * 1.2
241
- sip_10yr = amount / (10 * 12) * 1.1
242
-
243
- return f"""
244
- ## 🎯 Goal-Based Investment for ₹{amount:,.0f}
245
-
246
- **SIP Requirements:**
247
- • **5 years:** ₹{sip_5yr:,.0f}/month (assuming 12% returns)
248
- • **10 years:** ₹{sip_10yr:,.0f}/month (assuming 11% returns)
249
- • **15 years:** ₹{amount/(15*12)*1.05:,.0f}/month (assuming 10% returns)
250
-
251
- **Investment Strategy:**
252
- • **Short-term (1-3 years):** Debt funds, liquid funds
253
- • **Medium-term (3-7 years):** Balanced advantage funds, hybrid funds
254
- • **Long-term (7+ years):** Equity funds, index funds
255
-
256
- **Action Plan:**
257
- 1. Define exact goal and timeline
258
- 2. Start SIP immediately (time in market > timing market)
259
- 3. Increase SIP by 10% annually (step-up SIP)
260
- 4. Review progress every 6 months
261
- 5. Stay disciplined, avoid stopping SIPs during market downturns
262
- """
263
-
264
- return self._generate_general_advice(prompt)
265
-
266
- def _generate_general_advice(self, prompt: str) -> str:
267
- return """
268
- ## 💡 Personal Finance Fundamentals
269
-
270
- **Priority Order:**
271
- 1. **Emergency Fund** - 3-6 months expenses in savings account
272
- 2. **Insurance** - Term life + comprehensive health insurance
273
- 3. **Debt Elimination** - Pay off high-interest debt first
274
- 4. **Tax Planning** - Maximize deductions legally
275
- 5. **Goal-based Investing** - SIPs for specific goals
276
- 6. **Wealth Creation** - Additional investments after basics
277
-
278
- **General Principles:**
279
- • Start early, invest regularly
280
- • Diversify across asset classes
281
- • Keep costs low (expense ratios)
282
- • Stay disciplined, avoid emotional decisions
283
- • Review and rebalance annually
284
-
285
- **Common Mistakes to Avoid:**
286
- - Waiting for "right time" to invest
287
- - Putting all money in FDs/savings
288
- - Not having adequate insurance
289
- - Frequent buying/selling based on market news
290
- """
291
-
292
  class HuggingFaceProvider(AIProvider):
293
  def __init__(self):
294
  super().__init__()
295
  self.name = "huggingface"
296
  self.gen = None
297
-
298
- if HF_AVAILABLE and HF_PIPELINE:
299
  try:
300
- self.gen = HF_PIPELINE("text2text-generation", model="google/flan-t5-base")
301
- st.success("HuggingFace model loaded successfully!")
302
  except Exception as e:
303
- st.warning(f"HuggingFace pipeline failed: {e}")
304
- self.gen = None
305
  else:
306
- st.info("Using enhanced rule-based responses (HuggingFace not available)")
307
-
308
  def generate(self, prompt: str, max_tokens: int = 512) -> str:
309
  if self.gen is None:
310
- # Fallback to enhanced rule-based provider
311
- fallback_provider = EnhancedRuleBasedProvider()
312
- return fallback_provider.generate(prompt, max_tokens)
313
-
314
- try:
315
- result = self.gen(prompt, max_length=min(1024, max_tokens), do_sample=True, temperature=0.7)
316
- return result[0]["generated_text"].strip()
317
- except Exception as e:
318
- st.error(f"Generation error: {e}")
319
- fallback_provider = EnhancedRuleBasedProvider()
320
- return fallback_provider.generate(prompt, max_tokens)
321
 
322
  class IBMGraniteWatsonProvider(AIProvider):
323
  def __init__(self, watson_api_key: Optional[str], watson_url: Optional[str], granite_key: Optional[str]):
@@ -327,54 +63,23 @@ class IBMGraniteWatsonProvider(AIProvider):
327
  self.watson_api_key = watson_api_key
328
  self.watson_url = watson_url
329
  self.granite_key = granite_key
330
-
331
  def generate(self, prompt: str, max_tokens: int = 512) -> str:
332
  if not self.ok:
333
- st.warning("IBM credentials not provided, using enhanced rule-based responses")
334
- fallback_provider = EnhancedRuleBasedProvider()
335
- return fallback_provider.generate(prompt, max_tokens)
336
-
337
- # TODO: Implement actual IBM Granite/Watson API calls
338
- return f"""
339
- [IBM Granite/Watson Response - To be implemented with actual API]
340
-
341
- **Enhanced Rule-Based Response:**
342
- {EnhancedRuleBasedProvider().generate(prompt, max_tokens)}
343
- """
344
 
345
- # Enhanced categorization with more comprehensive categories
346
- CATEGORIES = {
347
- "income": ["salary", "stipend", "bonus", "interest", "dividend", "freelance", "rental"],
348
- "housing": ["rent", "landlord", "mortgage", "property tax", "home insurance"],
349
- "utilities": ["electric", "electricity", "water", "gas", "utility", "internet", "phone", "mobile"],
350
- "groceries": ["grocery", "supermarket", "food mart", "provisions", "vegetables", "fruits"],
351
- "transport": ["uber", "ola", "taxi", "fuel", "petrol", "diesel", "bus", "metro", "train", "auto"],
352
- "entertainment": ["netflix", "spotify", "amazon prime", "movie", "cinema", "theatre", "concert", "game"],
353
- "health": ["pharmacy", "doctor", "hospital", "clinic", "medicine", "medical", "dentist"],
354
- "eating_out": ["restaurant", "cafe", "bar", "eatery", "diner", "food delivery", "zomato", "swiggy"],
355
- "shopping": ["amazon", "flipkart", "myntra", "shopping", "clothes", "electronics", "appliances"],
356
- "education": ["school", "college", "university", "course", "books", "tuition"],
357
- "insurance": ["life insurance", "health insurance", "car insurance", "insurance premium"],
358
- "investments": ["mutual fund", "sip", "ppf", "fd", "shares", "stocks", "investment"],
359
- "loan_emi": ["emi", "loan", "credit card", "home loan", "car loan", "personal loan"]
360
- }
361
 
362
  def categorize(desc: str) -> str:
363
- """Enhanced categorization with better accuracy"""
364
- desc_l = (desc or "").lower().strip()
365
-
366
- # Direct keyword matching with priority
367
- for cat, keywords in CATEGORIES.items():
368
- for keyword in keywords:
369
- if keyword in desc_l:
370
- return cat
371
-
372
- # Partial matching for common patterns
373
- if any(word in desc_l for word in ["atm", "withdrawal", "cash"]):
374
- return "cash_withdrawal"
375
- if any(word in desc_l for word in ["transfer", "upi", "gpay", "paytm"]):
376
- return "transfers"
377
-
378
  return "other"
379
 
380
  @dataclass
@@ -386,441 +91,150 @@ class UserProfile:
386
  monthly_income: float
387
  risk_tolerance: str
388
  goals: str
389
-
390
  def style_prompt(self) -> str:
391
  if self.user_type.lower().startswith("stud"):
392
  return (
393
- "Explain in simple terms with practical examples. "
394
- "Focus on affordable options and student-specific benefits."
395
  )
396
  return (
397
- "Provide detailed analysis with specific numbers and actionable steps. "
398
- "Include advanced strategies and tax implications."
399
  )
400
-
401
- def get_investment_allocation(self) -> Dict[str, float]:
402
- """Get age-based investment allocation"""
403
- equity_percentage = max(20, min(90, 100 - self.age))
404
- debt_percentage = 100 - equity_percentage
405
-
406
- return {
407
- "equity": equity_percentage,
408
- "debt": debt_percentage,
409
- "recommended_sip": max(1000, self.monthly_income * 0.2)
410
- }
411
 
412
  def load_transactions(uploaded_file: Optional[io.BytesIO]) -> pd.DataFrame:
413
- """Load and process transaction data with enhanced sample data"""
414
  if uploaded_file is None:
415
- # More realistic sample data
416
- dates = pd.date_range("2025-07-01", periods=30, freq="D")
417
- sample_data = {
418
- "date": dates,
419
- "description": [
420
- "Salary Credit", "Rent Payment", "Grocery Shopping", "Restaurant Bill", "Metro Card Recharge",
421
- "Internet Bill", "Pharmacy", "Movie Tickets", "Amazon Purchase", "Fuel", "Freelance Income",
422
- "Electricity Bill", "Cafe Expense", "Supermarket", "Medical Checkup", "Netflix Subscription",
423
- "Uber Ride", "Water Bill", "Gym Membership", "Online Shopping", "Bus Pass", "Medicine",
424
- "Dividend Credit", "Train Ticket", "Food Delivery", "Mobile Recharge", "Book Purchase",
425
- "Insurance Premium", "SIP Investment", "ATM Withdrawal"
426
- ],
427
- "amount": [
428
- 75000, -18000, -3200, -1200, -500, -1200, -800, -600, -2500, -2000, 15000,
429
- -1500, -400, -2800, -3000, -699, -250, -300, -1500, -1800, -280, -450,
430
- 2500, -180, -450, -199, -350, -2500, -5000, -2000
431
- ]
432
- }
433
- df = pd.DataFrame(sample_data)
434
  else:
435
  try:
436
  df = pd.read_csv(uploaded_file)
437
- if 'date' not in df.columns or 'amount' not in df.columns or 'description' not in df.columns:
438
- st.error("CSV must have 'date', 'description', and 'amount' columns")
439
- return pd.DataFrame()
440
  except Exception as e:
441
- st.error(f"Error reading CSV: {e}")
442
- return pd.DataFrame()
443
-
444
- # Enhanced data processing
445
- df["date"] = pd.to_datetime(df["date"], errors='coerce')
446
- df["amount"] = pd.to_numeric(df["amount"], errors='coerce')
447
- df = df.dropna(subset=['date', 'amount'])
448
  df["category"] = df["description"].apply(categorize)
449
- df = df.sort_values('date')
450
-
451
  return df
452
 
453
  def budget_summary(df: pd.DataFrame, monthly_income_hint: Optional[float] = None) -> Dict[str, float]:
454
- """Enhanced budget summary with more detailed analysis"""
455
- if df.empty:
456
- return {"income_total": 0, "expense_total": 0, "net_savings": 0, "savings_rate_pct": 0, "top_spend_json": "{}"}
457
-
458
- # Calculate totals
459
  income = df.loc[df["amount"] > 0, "amount"].sum()
460
  expenses = -df.loc[df["amount"] < 0, "amount"].sum()
461
-
462
- # Use monthly income hint if provided and higher than calculated income
463
- if monthly_income_hint and monthly_income_hint > income:
464
- income = monthly_income_hint
465
-
466
  net = income - expenses
467
- savings_rate = (net / income * 100) if income > 0 else 0.0
468
-
469
- # Category-wise analysis
470
- expense_by_category = df[df["amount"] < 0].groupby("category")["amount"].sum().abs()
471
- top_expenses = expense_by_category.nlargest(5)
472
-
473
- # Additional metrics
474
- avg_transaction = df["amount"].mean()
475
- transaction_count = len(df)
476
-
477
  return {
478
  "income_total": float(round(income, 2)),
479
  "expense_total": float(round(expenses, 2)),
480
  "net_savings": float(round(net, 2)),
481
  "savings_rate_pct": float(round(savings_rate, 2)),
482
- "avg_transaction": float(round(avg_transaction, 2)),
483
- "transaction_count": transaction_count,
484
- "top_spend_json": top_expenses.to_json(),
485
- "expense_breakdown": expense_by_category.to_dict()
486
  }
487
 
488
  def spending_suggestions(df: pd.DataFrame, profile: UserProfile) -> List[str]:
489
- """Enhanced spending suggestions with personalized recommendations"""
490
  tips = []
491
  summary = budget_summary(df, monthly_income_hint=profile.monthly_income)
492
- allocation = profile.get_investment_allocation()
493
-
494
- # Emergency fund check
495
- if summary["net_savings"] < profile.monthly_income * 0.25:
496
- tips.append(f"💰 Priority: Build emergency fund of ₹{profile.monthly_income * 6:,.0f} (6 months expenses)")
497
-
498
- # Savings rate analysis
499
- if summary["savings_rate_pct"] < 20:
500
- tips.append(f"📊 Increase savings rate to 20%+ (currently {summary['savings_rate_pct']:.1f}%)")
501
- elif summary["savings_rate_pct"] > 50:
502
- tips.append("🎉 Excellent savings rate! Consider increasing investments for better returns")
503
-
504
- # Category-specific recommendations
505
- expense_breakdown = summary.get("expense_breakdown", {})
506
-
507
- for category, amount in expense_breakdown.items():
508
- percentage = (amount / profile.monthly_income) * 100
509
- if category == "eating_out" and percentage > 8:
510
- tips.append(f"🍽️ Dining out: ₹{amount:,.0f} ({percentage:.1f}% of income). Try meal prep 2-3 days/week")
511
- elif category == "transport" and percentage > 10:
512
- tips.append(f"🚗 Transport: ₹{amount:,.0f} ({percentage:.1f}% of income). Consider monthly passes or carpooling")
513
- elif category == "entertainment" and percentage > 5:
514
- tips.append(f"🎬 Entertainment: ₹{amount:,.0f} ({percentage:.1f}% of income). Look for free/cheaper alternatives")
515
-
516
- # Investment recommendations
517
- tips.append(f"📈 Recommended SIP: ₹{allocation['recommended_sip']:,.0f}/month ({allocation['equity']:.0f}% equity, {allocation['debt']:.0f}% debt)")
518
-
519
- # Age-specific advice
520
- if profile.age < 30:
521
- tips.append("🎯 Focus on building emergency fund, then aggressive equity investments")
522
- elif profile.age < 40:
523
- tips.append("⚖️ Balance growth and stability. Consider life insurance and goal-based planning")
524
- else:
525
- tips.append("🛡️ Focus on wealth preservation, reduce equity exposure gradually")
526
-
527
- # Risk-based advice
528
- risk_advice = {
529
- "low": "Conservative approach: 60% debt funds, 40% large-cap equity funds",
530
- "medium": "Balanced approach: 30% debt, 70% diversified equity funds",
531
- "high": "Aggressive approach: 10% debt, 90% equity (mix of large, mid, small-cap)"
532
- }
533
- tips.append(f"⚡ Risk-based allocation: {risk_advice.get(profile.risk_tolerance.lower(), 'Balanced approach recommended')}")
534
-
535
- return tips
536
 
537
- # Enhanced intent patterns
538
- INTENT_PATTERNS = {
539
- "savings": r"save|savings|emergency|fund|goal|target",
540
- "tax": r"tax|80c|deduction|income tax|regime|tds|refund|section",
541
- "investment": r"invest|sip|mutual fund|stock|index|portfolio|asset|equity|debt",
542
- "budget": r"budget|spend|expense|track|summary|report|category",
543
- "insurance": r"insurance|term|health|life|medical",
544
- "loan": r"loan|emi|credit|debt|mortgage|personal loan",
545
- "retirement": r"retirement|pension|pf|provident fund|nps"
546
- }
547
 
548
  def detect_intent(text: str) -> str:
549
- """Enhanced intent detection"""
550
- text_lower = text.lower()
551
-
552
- # Multiple intent scoring
553
- intent_scores = {}
554
- for intent, pattern in INTENT_PATTERNS.items():
555
- matches = len(re.findall(pattern, text_lower))
556
- if matches > 0:
557
- intent_scores[intent] = matches
558
-
559
- if intent_scores:
560
- return max(intent_scores, key=intent_scores.get)
561
-
562
  return "general"
563
 
564
  def build_system_prompt(profile: UserProfile) -> str:
565
- """Enhanced system prompt with detailed context"""
566
- allocation = profile.get_investment_allocation()
567
-
568
- return f"""
569
- You are an expert personal finance advisor for a {profile.user_type.lower()} in {profile.country}.
570
-
571
- User Profile:
572
- - Name: {profile.name}
573
- - Age: {profile.age} years
574
- - Monthly Income: ₹{profile.monthly_income:,.0f}
575
- - Risk Tolerance: {profile.risk_tolerance}
576
- - Financial Goals: {profile.goals}
577
- - Recommended Equity Allocation: {allocation['equity']:.0f}%
578
- - Recommended Monthly SIP: ₹{allocation['recommended_sip']:,.0f}
579
-
580
- Communication Style: {profile.style_prompt()}
581
-
582
- Guidelines:
583
- - Provide specific, actionable advice with numbers
584
- - Focus on Indian financial instruments and regulations
585
- - Emphasize emergency fund, insurance, and systematic investing
586
- - Use INR currency symbols
587
- - Mention tax implications where relevant
588
- - Always recommend consulting qualified professionals for complex situations
589
- - Provide educational content, not personalized financial advice
590
- """
591
 
592
  def craft_user_prompt(query: str, intent: str, summary: Dict[str, float]) -> str:
593
- """Enhanced user prompt with detailed context"""
594
- context = f"""
595
- Financial Summary:
596
- - Monthly Income: ₹{summary['income_total']:,.0f}
597
- - Monthly Expenses: ₹{summary['expense_total']:,.0f}
598
- - Net Savings: ₹{summary['net_savings']:,.0f}
599
- - Savings Rate: {summary['savings_rate_pct']:.1f}%
600
- - Average Transaction: ₹{summary.get('avg_transaction', 0):,.0f}
601
- - Total Transactions: {summary.get('transaction_count', 0)}
602
-
603
- Detected Intent: {intent}
604
- User Question: {query}
605
 
606
- Please provide a comprehensive response with:
607
- 1. Direct answer to the question
608
- 2. Explanation of why this matters
609
- 3. Specific action steps with amounts/timelines
610
- 4. Potential risks or considerations
611
- 5. Next steps for implementation
612
- """
613
-
614
- return context
615
-
616
- # Streamlit UI
617
  with st.sidebar:
618
- st.title("💰 Personal Finance Chatbot")
619
- st.caption("AI-Powered Financial Guidance")
620
-
621
- with st.expander("👤 User Profile", expanded=True):
622
- name = st.text_input("Name", value="User", key="name")
623
- user_type = st.selectbox("I am a", ["Professional", "Student", "Self-Employed", "Retired"], index=0)
624
- age = st.number_input("Age", min_value=18, max_value=80, value=28, step=1)
625
- country = st.selectbox("Country", ["India", "USA", "UK", "Canada"], index=0)
626
- monthly_income = st.number_input("Monthly Income (₹)", min_value=0, value=50000, step=5000)
627
- risk_tolerance = st.selectbox("Risk Tolerance", ["Low", "Medium", "High"], index=1)
628
- goals = st.text_area("Financial Goals",
629
- value="Emergency fund, Tax saving, Wealth creation, Retirement planning",
630
- height=100)
631
-
632
- st.markdown("---")
633
-
634
- with st.expander("🤖 AI Provider Settings"):
635
- provider_choice = st.selectbox(
636
- "AI Provider",
637
- ["Enhanced Rule-Based", "Auto (IBM→HF→Rule)", "IBM Granite/Watson", "HuggingFace"],
638
- index=0
639
- )
640
-
641
- with st.expander("📊 Data Upload"):
642
- uploaded = st.file_uploader(
643
- "Upload Transaction CSV",
644
- type=["csv"],
645
- help="CSV should have columns: date, description, amount"
646
- )
647
-
648
- if st.button("📥 Download Sample CSV"):
649
- sample_csv = """date,description,amount
650
- 2025-01-01,Salary Credit,75000
651
- 2025-01-02,Rent Payment,-20000
652
- 2025-01-03,Grocery Shopping,-3500
653
- 2025-01-04,Restaurant,-1200
654
- 2025-01-05,SIP Investment,-5000
655
- 2025-01-06,Utility Bill,-1500"""
656
-
657
- st.download_button(
658
- label="Download Sample",
659
- data=sample_csv,
660
- file_name="sample_transactions.csv",
661
- mime="text/csv"
662
- )
663
 
664
- # Initialize user profile
665
  profile = UserProfile(
666
- name=name, user_type=user_type, age=int(age), country=country,
667
- monthly_income=float(monthly_income), risk_tolerance=risk_tolerance, goals=goals
668
  )
669
 
670
- # Initialize AI providers
671
- if not st.session_state.provider_inited:
672
- ibm_provider = IBMGraniteWatsonProvider(
673
  watson_api_key=os.getenv("IBM_WATSON_API_KEY"),
674
  watson_url=os.getenv("IBM_WATSON_URL"),
675
  granite_key=os.getenv("IBM_GRANITE_API_KEY"),
676
  )
677
- hf_provider = HuggingFaceProvider()
678
- enhanced_provider = EnhancedRuleBasedProvider()
679
-
680
- # Provider selection logic
681
- if provider_choice == "Enhanced Rule-Based":
682
- chosen_provider = enhanced_provider
683
- elif provider_choice.startswith("IBM") and ibm_provider.ok:
684
- chosen_provider = ibm_provider
685
- elif provider_choice.startswith("HuggingFace"):
686
- chosen_provider = hf_provider
687
  elif provider_choice.startswith("Auto"):
688
- if ibm_provider.ok:
689
- chosen_provider = ibm_provider
690
- elif hf_provider.gen is not None:
691
- chosen_provider = hf_provider
692
- else:
693
- chosen_provider = enhanced_provider
694
- else:
695
- chosen_provider = enhanced_provider
696
-
697
- st.session_state.current_provider = chosen_provider
698
- st.session_state.provider_inited = True
699
-
700
- # Main layout
701
- col_chat, col_right = st.columns([0.6, 0.4])
 
 
 
702
 
 
703
  with col_right:
704
- st.subheader("📊 Financial Dashboard")
705
-
706
- # Load and display transaction data
707
  df = load_transactions(uploaded)
708
-
709
- if not df.empty:
710
- st.dataframe(df.tail(10), use_container_width=True, height=200)
711
-
712
- # Financial metrics
713
- summary = budget_summary(df, monthly_income_hint=profile.monthly_income)
714
-
715
- col1, col2 = st.columns(2)
716
- with col1:
717
- st.metric("Monthly Income", f"₹{summary['income_total']:,.0f}")
718
- st.metric("Net Savings", f"₹{summary['net_savings']:,.0f}")
719
- with col2:
720
- st.metric("Monthly Expenses", f"₹{summary['expense_total']:,.0f}")
721
- st.metric("Savings Rate", f"{summary['savings_rate_pct']:.1f}%")
722
-
723
- # Spending breakdown chart
724
- if summary.get('expense_breakdown'):
725
- st.subheader("💸 Expense Breakdown")
726
- expense_df = pd.DataFrame(
727
- list(summary['expense_breakdown'].items()),
728
- columns=['Category', 'Amount']
729
- )
730
- expense_df = expense_df.sort_values('Amount', ascending=True)
731
- st.bar_chart(expense_df.set_index('Category'))
732
-
733
- # AI Suggestions
734
- st.subheader("🧠 Personalized Suggestions")
735
- suggestions = spending_suggestions(df, profile)
736
- for i, tip in enumerate(suggestions, 1):
737
- st.write(f"{i}. {tip}")
738
- else:
739
- st.warning("No transaction data available")
740
 
741
  with col_chat:
742
- st.subheader("💬 Financial Assistant")
743
- st.caption(f"Powered by: {st.session_state.current_provider.name}")
744
-
745
- # Display chat history
746
- for turn in st.session_state.chat_history:
747
  with st.chat_message(turn["role"]):
748
  st.markdown(turn["content"])
749
-
750
- # Chat input
751
- user_msg = st.chat_input("Ask about investments, taxes, budgeting, or any financial topic...")
752
-
753
  if user_msg:
754
- # Add user message to chat history
755
- st.session_state.chat_history.append({"role": "user", "content": user_msg})
756
-
757
- # Detect intent and generate response
758
  intent = detect_intent(user_msg)
759
- summary = budget_summary(df, monthly_income_hint=profile.monthly_income) if not df.empty else {}
760
-
761
  sys_prompt = build_system_prompt(profile)
762
  usr_prompt = craft_user_prompt(user_msg, intent, summary)
763
  final_prompt = sys_prompt + "\n\n" + usr_prompt
764
-
765
- # Generate AI response
766
  with st.chat_message("assistant"):
767
- with st.spinner(f"Analyzing with {st.session_state.current_provider.name}..."):
768
  try:
769
- ai_response = st.session_state.current_provider.generate(final_prompt, max_tokens=1000)
770
  except Exception as e:
771
- ai_response = f"I encountered an error: {e}\nLet me provide some general guidance instead.\n\n{EnhancedRuleBasedProvider().generate(user_msg)}"
772
-
773
- st.markdown(ai_response)
774
-
775
- # Add AI response to chat history
776
- st.session_state.chat_history.append({"role": "assistant", "content": ai_response})
777
-
778
- # Sidebar quick actions
779
- with st.sidebar:
780
- st.markdown("---")
781
- st.subheader("🚀 Quick Actions")
782
-
783
- if st.button("🗑️ Clear Chat History"):
784
- st.session_state.chat_history = []
785
- st.rerun()
786
-
787
- if st.button("📊 Generate Financial Report"):
788
- if not df.empty:
789
- summary = budget_summary(df, monthly_income_hint=profile.monthly_income)
790
- suggestions = spending_suggestions(df, profile)
791
-
792
- report = f"""
793
- # Financial Health Report for {profile.name}
794
-
795
- ## Summary
796
- - Monthly Income: ₹{summary['income_total']:,.0f}
797
- - Monthly Expenses: ₹{summary['expense_total']:,.0f}
798
- - Net Savings: ₹{summary['net_savings']:,.0f}
799
- - Savings Rate: {summary['savings_rate_pct']:.1f}%
800
-
801
- ## Recommendations
802
- {chr(10).join(f"- {tip}" for tip in suggestions[:5])}
803
-
804
- ## Investment Allocation
805
- - Equity: {profile.get_investment_allocation()['equity']:.0f}%
806
- - Debt: {profile.get_investment_allocation()['debt']:.0f}%
807
- - Recommended SIP: ₹{profile.get_investment_allocation()['recommended_sip']:,.0f}/month
808
- """
809
-
810
- st.download_button(
811
- "📄 Download Report",
812
- report,
813
- file_name=f"financial_report_{profile.name}.md",
814
- mime="text/markdown"
815
- )
816
- else:
817
- st.warning("Upload transaction data to generate report")
818
 
819
- # Footer
820
- st.markdown("---")
821
  st.markdown("""
822
- <div style='text-align: center; color: #666666; font-size: 12px;'>
823
- <p><strong>Important Disclaimer:</strong> This chatbot provides educational information only and is not financial advice.
824
- Always consult qualified financial advisors for personalized guidance. Market investments are subject to risks.</p>
825
- </div>
826
- """, unsafe_allow_html=True)
 
1
  import os
2
  import io
3
  import re
 
4
  from dataclasses import dataclass
5
  from typing import List, Dict, Optional
 
6
  import pandas as pd
7
  import streamlit as st
8
 
9
+ # HF transformers optional import
10
  try:
11
  from transformers import pipeline
12
  HF_AVAILABLE = True
 
 
 
 
13
  except Exception:
14
  HF_AVAILABLE = False
 
15
 
16
+ # Streamlit config
17
+ st.set_page_config(page_title="Personal Finance Chatbot", page_icon="💬", layout="wide")
18
 
19
+ # Session keys robust initialization
20
  if "chat_history" not in st.session_state:
21
+ st.session_state["chat_history"] = []
22
+ if "providers" not in st.session_state:
23
+ st.session_state["providers"] = {}
24
  if "provider_inited" not in st.session_state:
25
+ st.session_state["provider_inited"] = False
26
  if "provider_name" not in st.session_state:
27
+ st.session_state["provider_name"] = "huggingface"
28
+
29
 
30
  class AIProvider:
31
  def __init__(self):
32
  self.name = "base"
 
33
  def generate(self, prompt: str, max_tokens: int = 512) -> str:
34
  raise NotImplementedError
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  class HuggingFaceProvider(AIProvider):
37
  def __init__(self):
38
  super().__init__()
39
  self.name = "huggingface"
40
  self.gen = None
41
+ if HF_AVAILABLE:
 
42
  try:
43
+ self.gen = pipeline("text2text-generation", model="google/flan-t5-base")
 
44
  except Exception as e:
45
+ st.warning(f"HuggingFace pipeline failed to load: {e}")
 
46
  else:
47
+ st.info("Transformers not installed; responses will be rule‑based only.")
 
48
  def generate(self, prompt: str, max_tokens: int = 512) -> str:
49
  if self.gen is None:
50
+ return (
51
+ "[Rule-based fallback]\n"
52
+ + prompt[:1000]
53
+ + "\n\n(Summarized suggestion) Consider tracking expenses, setting goals, building an emergency fund, and using diversified, low-cost index funds aligned with your risk tolerance.)"
54
+ )
55
+ out = self.gen(prompt, max_length=min(1024, max_tokens), do_sample=False)
56
+ return out["generated_text"].strip()
 
 
 
 
57
 
58
  class IBMGraniteWatsonProvider(AIProvider):
59
  def __init__(self, watson_api_key: Optional[str], watson_url: Optional[str], granite_key: Optional[str]):
 
63
  self.watson_api_key = watson_api_key
64
  self.watson_url = watson_url
65
  self.granite_key = granite_key
 
66
  def generate(self, prompt: str, max_tokens: int = 512) -> str:
67
  if not self.ok:
68
+ return "[IBM placeholder] Missing credentials falling back text.\n" + prompt
69
+ # Placeholder for real IBM SDK call
70
+ return (
71
+ "[IBM Granite/Watson simulated response]\n"
72
+ "(Replace this with real SDK call)\n\n"
73
+ + prompt
74
+ )
 
 
 
 
75
 
76
+ CATEGORIES = { ... } # leave as in original
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  def categorize(desc: str) -> str:
79
+ desc_l = (desc or "").lower()
80
+ for cat, keys in CATEGORIES.items():
81
+ if any(k in desc_l for k in keys):
82
+ return cat
 
 
 
 
 
 
 
 
 
 
 
83
  return "other"
84
 
85
  @dataclass
 
91
  monthly_income: float
92
  risk_tolerance: str
93
  goals: str
 
94
  def style_prompt(self) -> str:
95
  if self.user_type.lower().startswith("stud"):
96
  return (
97
+ "Explain like a friendly mentor to a student. Keep it clear and concise, "
98
+ "use practical examples and low‑jargon."
99
  )
100
  return (
101
+ "Explain like a professional financial coach. Be precise, structured, and include "
102
+ "brief rationale with trade‑offs."
103
  )
 
 
 
 
 
 
 
 
 
 
 
104
 
105
  def load_transactions(uploaded_file: Optional[io.BytesIO]) -> pd.DataFrame:
 
106
  if uploaded_file is None:
107
+ # Demo data
108
+ data = { ... } # as per original
109
+ df = pd.DataFrame(data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  else:
111
  try:
112
  df = pd.read_csv(uploaded_file)
 
 
 
113
  except Exception as e:
114
+ st.error(f"Could not read CSV: {e}. Showing demo data.")
115
+ data = { ... }
116
+ df = pd.DataFrame(data)
 
 
 
 
117
  df["category"] = df["description"].apply(categorize)
 
 
118
  return df
119
 
120
  def budget_summary(df: pd.DataFrame, monthly_income_hint: Optional[float] = None) -> Dict[str, float]:
121
+ df["month"] = pd.to_datetime(df["date"]).dt.to_period("M").astype(str)
 
 
 
 
122
  income = df.loc[df["amount"] > 0, "amount"].sum()
123
  expenses = -df.loc[df["amount"] < 0, "amount"].sum()
 
 
 
 
 
124
  net = income - expenses
125
+ if monthly_income_hint and monthly_income_hint > 0:
126
+ income = max(income, monthly_income_hint)
127
+ net = income - expenses
128
+ savings_rate = (net / income) * 100 if income > 0 else 0.0
129
+ by_cat = df.groupby("category")["amount"].sum().sort_values()
130
+ top_spend = (-by_cat[by_cat < 0]).nlargest(5)
 
 
 
 
131
  return {
132
  "income_total": float(round(income, 2)),
133
  "expense_total": float(round(expenses, 2)),
134
  "net_savings": float(round(net, 2)),
135
  "savings_rate_pct": float(round(savings_rate, 2)),
136
+ "top_spend_json": top_spend.to_json(),
 
 
 
137
  }
138
 
139
  def spending_suggestions(df: pd.DataFrame, profile: UserProfile) -> List[str]:
 
140
  tips = []
141
  summary = budget_summary(df, monthly_income_hint=profile.monthly_income)
142
+ # ... rest unchanged
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
+ INTENT_PATTERNS = { ... }
 
 
 
 
 
 
 
 
 
145
 
146
  def detect_intent(text: str) -> str:
147
+ t = text.lower()
148
+ for k, pat in INTENT_PATTERNS.items():
149
+ if re.search(pat, t):
150
+ return k
 
 
 
 
 
 
 
 
 
151
  return "general"
152
 
153
  def build_system_prompt(profile: UserProfile) -> str:
154
+ # ... unchanged
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
  def craft_user_prompt(query: str, intent: str, summary: Dict[str, float]) -> str:
157
+ # ... unchanged
 
 
 
 
 
 
 
 
 
 
 
158
 
 
 
 
 
 
 
 
 
 
 
 
159
  with st.sidebar:
160
+ # Inputs (unchanged)
161
+ # ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
 
163
  profile = UserProfile(
164
+ # ... unchanged
 
165
  )
166
 
167
+ # Provider initialization block
168
+ if not st.session_state["provider_inited"]:
169
+ st.session_state["providers"]["ibm"] = IBMGraniteWatsonProvider(
170
  watson_api_key=os.getenv("IBM_WATSON_API_KEY"),
171
  watson_url=os.getenv("IBM_WATSON_URL"),
172
  granite_key=os.getenv("IBM_GRANITE_API_KEY"),
173
  )
174
+ st.session_state["providers"]["hf"] = HuggingFaceProvider()
175
+ # Choose provider based on user choice and credentials
176
+ chosen = "huggingface"
177
+ ibm_ok = st.session_state["providers"]["ibm"].ok
178
+ if provider_choice.startswith("IBM") and ibm_ok:
179
+ chosen = "ibm_granite_watson"
 
 
 
 
180
  elif provider_choice.startswith("Auto"):
181
+ chosen = "ibm_granite_watson" if ibm_ok else "huggingface"
182
+ st.session_state["provider_name"] = chosen
183
+ st.session_state["provider_inited"] = True
184
+
185
+ # Always pick the right provider, even after sidebar changes
186
+ provider_name = st.session_state["provider_name"]
187
+ if provider_choice.startswith("IBM"):
188
+ provider_name = "ibm_granite_watson" if st.session_state["providers"]["ibm"].ok else "huggingface"
189
+ elif provider_choice.startswith("Auto"):
190
+ provider_name = "ibm_granite_watson" if st.session_state["providers"]["ibm"].ok else "huggingface"
191
+ else:
192
+ provider_name = "huggingface"
193
+ st.session_state["provider_name"] = provider_name
194
+ provider = (
195
+ st.session_state["providers"]["ibm"] if provider_name == "ibm_granite_watson"
196
+ else st.session_state["providers"]["hf"]
197
+ )
198
 
199
+ col_chat, col_right = st.columns([0.62, 0.38])
200
  with col_right:
201
+ st.subheader("📊 Budget Summary")
 
 
202
  df = load_transactions(uploaded)
203
+ st.dataframe(df, use_container_width=True, height=250)
204
+ summary = budget_summary(df, monthly_income_hint=profile.monthly_income)
205
+ m1, m2, m3, m4 = st.columns(4)
206
+ m1.metric("Income (₹)", f"{summary['income_total']:.0f}")
207
+ m2.metric("Expenses (���)", f"{summary['expense_total']:.0f}")
208
+ m3.metric("Net (₹)", f"{summary['net_savings']:.0f}")
209
+ m4.metric("Savings Rate", f"{summary['savings_rate_pct']:.1f}%")
210
+ st.markdown("### 🧠 AI Spending Suggestions")
211
+ for tip in spending_suggestions(df, profile):
212
+ st.write(" ", tip)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
  with col_chat:
215
+ st.subheader("🗣️ Ask about savings, taxes, investments, or budgeting")
216
+ for turn in st.session_state["chat_history"]:
 
 
 
217
  with st.chat_message(turn["role"]):
218
  st.markdown(turn["content"])
219
+ user_msg = st.chat_input("Type your question… (e.g., How much should I invest monthly for a ₹10L goal?)")
 
 
 
220
  if user_msg:
221
+ st.session_state["chat_history"].append({"role": "user", "content": user_msg})
 
 
 
222
  intent = detect_intent(user_msg)
 
 
223
  sys_prompt = build_system_prompt(profile)
224
  usr_prompt = craft_user_prompt(user_msg, intent, summary)
225
  final_prompt = sys_prompt + "\n\n" + usr_prompt
 
 
226
  with st.chat_message("assistant"):
227
+ with st.spinner(f"Thinking with {provider.name}"):
228
  try:
229
+ ai = provider.generate(final_prompt, max_tokens=768)
230
  except Exception as e:
231
+ ai = f"Provider error: {e}\nFalling back to heuristic guidance.\n" + usr_prompt
232
+ st.markdown(ai)
233
+ st.session_state["chat_history"].append({"role": "assistant", "content": ai})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
 
 
235
  st.markdown("""
236
+ ---
237
+ **Disclaimers**
238
+ This chatbot provides educational information only and is **not** financial, tax, or legal advice.
239
+ Tax rules change frequently; consult a qualified professional for personalized advice.
240
+ """)