Rahul2298 commited on
Commit
d4cf530
·
verified ·
1 Parent(s): e3ac55b

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +240 -101
src/streamlit_app.py CHANGED
@@ -1,109 +1,248 @@
 
 
 
 
1
  import streamlit as st
2
- from transformers import pipeline
3
-
4
- # ---- Load HuggingFace Model ----
5
- @st.cache_resource
6
- def load_model():
7
- try:
8
- # Try Falcon-7B first (best results, requires GPU)
9
- return pipeline(
10
- "text-generation",
11
- model="tiiuae/falcon-7b-instruct",
12
- device_map="auto",
13
- trust_remote_code=True
14
- )
15
- except Exception as e1:
16
- st.warning("⚠️ Falcon-7B could not load. Falling back to FLAN-T5-Base (lighter).")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  try:
18
- return pipeline("text2text-generation", model="google/flan-t5-base")
19
- except Exception as e2:
20
- st.error(f"❌ Failed to load any model.\nFalcon error: {e1}\nFLAN error: {e2}")
21
- return None
22
-
23
- generator = load_model()
24
-
25
- # ---- Build prompt ----
26
- def build_prompt(user_input, profile, summary, is_t5=False):
27
- profile_text = (
28
- f"You are FinanceAI, an expert Indian financial advisor.\n\n"
29
- f"Profile:\n"
30
- f"Age: {profile['age']}, "
31
- f"Occupation: {profile['occupation']}, "
32
- f"Income: ₹{profile['income']}/month, "
33
- f"Risk Appetite: {profile['risk_appetite']}, "
34
- f"Goals: {', '.join(profile['goals'])}\n\n"
35
- )
36
- summary_text = f"Financial Summary:\n{summary}\n\n" if summary else ""
37
- if is_t5:
38
- return f"{profile_text}{summary_text}Question: {user_input}\nAnswer with step-by-step financial advice."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  else:
40
- return f"{profile_text}{summary_text}User question: {user_input}\n\nGive clear, step-by-step financial advice with examples."
41
-
42
- # ---- Streamlit UI ----
43
- def main():
44
- st.set_page_config(page_title="FinanceAI Chatbot", layout="wide")
45
- st.title("💰 Personal Finance Chatbot")
46
- st.write("This chatbot runs on **Hugging Face Spaces**.\n\n"
47
- "It tries **Falcon-7B-Instruct** (GPU) first, then falls back to **FLAN-T5-Base** (CPU-friendly).")
48
-
49
- # --- Initialize session state ---
50
- if "profile" not in st.session_state:
51
- st.session_state.profile = {
52
- "age": 25,
53
- "occupation": "Student",
54
- "income": 50000,
55
- "risk_appetite": "Moderate",
56
- "goals": [],
57
- }
58
- if "summary" not in st.session_state:
59
- st.session_state.summary = "Income ₹50,000; Expenses ₹30,000; Savings ₹20,000"
60
- if "chat_history" not in st.session_state:
61
- st.session_state.chat_history = []
62
-
63
- # Sidebar profile setup
64
- st.sidebar.header("👤 User Profile")
65
- st.session_state.profile["age"] = st.sidebar.number_input("Age", min_value=18, max_value=100, value=st.session_state.profile["age"])
66
- st.session_state.profile["occupation"] = st.sidebar.selectbox("Occupation", ["Student", "Salaried", "Freelancer", "Business Owner"], index=["Student", "Salaried", "Freelancer", "Business Owner"].index(st.session_state.profile["occupation"]))
67
- st.session_state.profile["income"] = st.sidebar.number_input("Monthly Income (₹)", min_value=0, value=st.session_state.profile["income"])
68
- st.session_state.profile["risk_appetite"] = st.sidebar.selectbox("Risk Appetite", ["Low", "Moderate", "High"], index=["Low", "Moderate", "High"].index(st.session_state.profile["risk_appetite"]))
69
- st.session_state.profile["goals"] = st.sidebar.multiselect("Financial Goals", ["Emergency Fund", "Retirement", "Travel", "Home", "Education", "Wealth Growth"], default=st.session_state.profile["goals"])
70
-
71
- st.session_state.summary = st.sidebar.text_area("📊 Financial Summary", st.session_state.summary)
72
-
73
- # Chat section
74
- st.header("💬 Chat with FinanceAI")
75
-
76
- user_input = st.text_input("Ask your financial question:")
77
-
78
- if st.button("Send") and user_input:
79
- if generator:
80
- is_t5 = "text2text" in str(type(generator))
81
- prompt = build_prompt(user_input, st.session_state.profile, st.session_state.summary, is_t5=is_t5)
82
-
83
- if is_t5:
84
- response = generator(prompt, max_length=256)
85
- ai_response = response[0]['generated_text']
86
- else:
87
- response = generator(
88
- prompt,
89
- max_length=512,
90
- do_sample=True,
91
- top_p=0.9,
92
- temperature=0.6
93
- )
94
- ai_response = response[0]['generated_text'].replace(prompt, "").strip()
95
  else:
96
- ai_response = "⚠️ No model available. Please check setup."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- st.session_state.chat_history.append({"role": "user", "content": user_input})
99
- st.session_state.chat_history.append({"role": "ai", "content": ai_response})
 
 
 
 
 
 
 
 
 
100
 
101
- # Display chat history
102
- for msg in st.session_state.chat_history:
103
- if msg["role"] == "user":
104
- st.markdown(f"**👤 You:** {msg['content']}")
 
 
 
 
 
 
 
 
 
105
  else:
106
- st.markdown(f"**🤖 FinanceAI:** {msg['content']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- if __name__ == "__main__":
109
- main()
 
 
 
 
1
+ import os
2
+ import io
3
+ import re
4
+ import pandas as pd
5
  import streamlit as st
6
+ from dataclasses import dataclass
7
+ from typing import List, Dict, Optional
8
+
9
+ try:
10
+ from transformers import pipeline
11
+ HF_AVAILABLE = True
12
+ except Exception:
13
+ HF_AVAILABLE = False
14
+
15
+ # -------- SESSION STATE --------
16
+ if "chat_history" not in st.session_state:
17
+ st.session_state.chat_history = []
18
+
19
+ # --------- USER PROFILE ---------
20
+ @dataclass
21
+ class UserProfile:
22
+ name: str
23
+ user_type: str # "Student"/"Professional"
24
+ age: int
25
+ country: str
26
+ monthly_income: float
27
+ risk: str # "Low"/"Medium"/"High"
28
+ goals: str
29
+ def style_prompt(self):
30
+ if self.user_type.lower().startswith("stud"):
31
+ return "Respond as a friendly mentor to a student. Use clear, simple, supportive language, with practical examples."
32
+ return "Respond as a professional financial advisor for a working adult. Use precise, structured language, include trade-offs."
33
+
34
+ # --------- DATA & CATEGORIZATION ---------
35
+ CATEGORIES = {
36
+ "groceries": ["grocery", "supermarket", "food", "mart"],
37
+ "rent": ["rent", "landlord"],
38
+ "utilities": ["electric", "water", "gas", "utility", "internet"],
39
+ "transport": ["uber", "ola", "fuel", "bus", "metro", "train", "cab", "petrol"],
40
+ "entertainment": ["netflix", "spotify", "movie", "cinema", "concert", "game"],
41
+ "health": ["pharmacy", "doctor", "hospital", "clinic", "medicine"],
42
+ "eating_out": ["restaurant", "cafe", "bar", "eatery", "diner"],
43
+ "shopping": ["amazon", "flipkart", "myntra", "shop", "store"],
44
+ "income": ["salary", "stipend", "bonus", "interest", "dividend"],
45
+ }
46
+ def categorize(desc: str) -> str:
47
+ desc_l = (desc or "").lower()
48
+ for cat, keys in CATEGORIES.items():
49
+ if any(k in desc_l for k in keys):
50
+ return cat
51
+ return "other"
52
+
53
+ def load_transactions(uploaded_file: Optional[io.BytesIO]) -> pd.DataFrame:
54
+ # Demo data for new users or failed upload:
55
+ data = {
56
+ "date": pd.date_range("2025-07-01", periods=24, freq="D"),
57
+ "description": [
58
+ "Salary", "Rent", "Grocery Store", "Restaurant", "Metro Card", "Internet Bill",
59
+ "Pharmacy", "Movie", "Amazon", "Fuel", "Bonus", "Electric Bill",
60
+ "Café", "Supermarket", "Hospital", "Netflix", "Ola Ride", "Water Bill",
61
+ "Gym", "Flipkart", "Bus", "Medicine", "Dividend", "Train"
62
+ ],
63
+ "amount": [
64
+ 70000, -15000, -2500, -900, -300, -800, -1200, -500, -2200, -1500, 8000, -1200,
65
+ -450, -2100, -5000, -500, -350, -400, -1200, -1800, -200, -600, 1200, -250
66
+ ],
67
+ }
68
+ if uploaded_file is None:
69
+ df = pd.DataFrame(data)
70
+ else:
71
  try:
72
+ df = pd.read_csv(uploaded_file)
73
+ except Exception:
74
+ df = pd.DataFrame(data)
75
+ df["category"] = df["description"].apply(categorize)
76
+ return df
77
+
78
+ def budget_summary(df: pd.DataFrame, monthly_income_hint: Optional[float]=None) -> Dict[str, float]:
79
+ income = df.loc[df["amount"] > 0, "amount"].sum()
80
+ expenses = -df.loc[df["amount"] < 0, "amount"].sum()
81
+ net = income - expenses
82
+ if monthly_income_hint and monthly_income_hint > 0:
83
+ income = max(income, monthly_income_hint)
84
+ net = income - expenses
85
+ savings_rate = (net / income) * 100 if income > 0 else 0.0
86
+ top_spend = (-df[df["amount"] < 0].groupby("category")["amount"].sum()).nlargest(5)
87
+ return {
88
+ "income_total": float(round(income, 2)),
89
+ "expense_total": float(round(expenses, 2)),
90
+ "net_savings": float(round(net, 2)),
91
+ "savings_rate_pct": float(round(savings_rate, 2)),
92
+ "top_spend_json": top_spend.to_json(),
93
+ }
94
+
95
+ def spending_suggestions(df: pd.DataFrame, profile: UserProfile) -> List[str]:
96
+ tips = []
97
+ summary = budget_summary(df, monthly_income_hint=profile.monthly_income)
98
+ if summary["net_savings"] < profile.monthly_income * 0.1:
99
+ tips.append("Build or maintain a 3–6 month emergency fund; automate a monthly transfer to high‑yield savings.")
100
+ cat_spend = -df[df["amount"] < 0].groupby("category")["amount"].sum()
101
+ for cat, amt in cat_spend.sort_values(ascending=False).head(3).items():
102
+ if amt > profile.monthly_income * 0.15:
103
+ tips.append(f"{cat.capitalize()} spending is high (₹{int(amt)}): Set a spending cap and leverage cash-back offers where possible.")
104
+ eat_out = -df[(df["category"] == "eating_out") & (df["amount"] < 0)]["amount"].sum()
105
+ if eat_out > 0.07 * profile.monthly_income:
106
+ tips.append("You are spending >7% of income on eating out. Consider meal planning and limit eating out to weekends.")
107
+ transport = -df[(df["category"] == "transport") & (df["amount"] < 0)]["amount"].sum()
108
+ if transport > 0.08 * profile.monthly_income:
109
+ tips.append("Transport spend is sizable. Consider monthly passes, rideshares or optimizing travel days.")
110
+ if profile.risk.lower() == "low":
111
+ tips.append("Consider a conservative portfolio: higher allocation to bonds, fixed income, low volatility funds.")
112
+ elif profile.risk.lower() == "high":
113
+ tips.append("For high risk tolerance: diversify, use low-cost index funds with limited exposure to growth sectors.")
114
+ if profile.user_type.lower().startswith("stud"):
115
+ tips.append("As a student, use student discounts, avoid high-interest credit, and keep credit utilization <30%.")
116
  else:
117
+ tips.append("As a professional, automate investments, optimize tax, and annually review insurance cover.")
118
+ return tips
119
+
120
+ # --- INTENT FILTER (Optional, for finance/numbers only) ---
121
+ FINANCE_KEYWORDS = ["finance", "money", "budget", "expense", "savings", "tax", "investment", "loan", "credit", "debit", "stock", "rate", "income", "emi", "pay", "salary", "roi", "interest", "dividend", "bond", "sip", "fd", "rd", "fixed deposit", "asset", "liability", "capital"]
122
+
123
+ def is_finance_related(text):
124
+ text_l = text.lower()
125
+ if any(word in text_l for word in FINANCE_KEYWORDS):
126
+ return True
127
+ if any(char.isdigit() for char in text):
128
+ return True
129
+ return False
130
+
131
+ # ----------- AI PROVIDER WRAPPERS -------------
132
+ class HuggingFaceProvider:
133
+ def __init__(self):
134
+ if HF_AVAILABLE:
135
+ try:
136
+ self.gen = pipeline("text2text-generation", model="google/flan-t5-base")
137
+ except Exception:
138
+ self.gen = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  else:
140
+ self.gen = None
141
+ self.name = "huggingface"
142
+ def generate(self, prompt, max_tokens=512):
143
+ if self.gen is None:
144
+ return ("[Fallback] Unable to answer with LLM. Please try again later.")
145
+ out = self.gen(prompt, max_length=min(1024, max_tokens), do_sample=False)
146
+ return out[0]['generated_text'].strip()
147
+
148
+ class GraniteWatsonProvider:
149
+ def __init__(self):
150
+ # These env vars are expected to be set on Hugging Face Spaces for secure production
151
+ self.api_key = os.getenv("IBM_WATSON_API_KEY", "")
152
+ self.url = os.getenv("IBM_WATSON_URL", "")
153
+ self.name = "granite_watson"
154
+ def ok(self):
155
+ return bool(self.api_key and self.url)
156
+ def generate(self, prompt, max_tokens=512):
157
+ # NO actual API call for demo/cost reasons – replace with real SDK/API in prod
158
+ return "[Granite/Watson Simulated Response]\n\n" + prompt
159
+
160
+ # ----------- STREAMLIT UI ----------------------
161
+ st.set_page_config(page_title="FinanceBot", page_icon="💸", layout="wide")
162
+
163
+ with st.sidebar:
164
+ st.title("💸 FinanceBot")
165
+ name = st.text_input("Name", value="Rahul")
166
+ user_type = st.selectbox("You are a", ["Student", "Professional"], index=1)
167
+ age = st.number_input("Age", min_value=16, max_value=90, value=24)
168
+ country = st.text_input("Country", value="India")
169
+ monthly_income = st.number_input("Monthly Income (₹)", min_value=0, value=70000, step=1000)
170
+ risk = st.selectbox("Risk Tolerance", ["Low", "Medium", "High"], index=1)
171
+ goals = st.text_area("Goals (comma-separated)", value="build emergency fund, start SIP, save tax")
172
+ provider_choice = st.selectbox("AI Provider", ["HuggingFace", "Granite/Watson"], index=0)
173
+ uploaded = st.file_uploader("Transaction CSV (date,description,amount)", type=["csv"])
174
+
175
+ profile = UserProfile(
176
+ name=name, user_type=user_type, age=int(age), country=country,
177
+ monthly_income=float(monthly_income), risk=risk, goals=goals
178
+ )
179
+ df = load_transactions(uploaded)
180
+ summary = budget_summary(df, monthly_income_hint=profile.monthly_income)
181
+
182
+ # Providers
183
+ hf_provider = HuggingFaceProvider()
184
+ granite_provider = GraniteWatsonProvider()
185
+ provider = hf_provider if provider_choice == "HuggingFace" else granite_provider
186
+
187
+ # ----------- MAIN UI: Chat and Results -----------
188
+ col_chat, col_right = st.columns([0.62, 0.38])
189
 
190
+ with col_right:
191
+ st.subheader("📊 Budget Summary")
192
+ st.dataframe(df, use_container_width=True, height=240)
193
+ m1, m2, m3, m4 = st.columns(4)
194
+ m1.metric("Income (₹)", f"{summary['income_total']:.0f}")
195
+ m2.metric("Expenses (₹)", f"{summary['expense_total']:.0f}")
196
+ m3.metric("Net (₹)", f"{summary['net_savings']:.0f}")
197
+ m4.metric("Savings Rate", f"{summary['savings_rate_pct']}%")
198
+ st.markdown("#### 🧠 Spending & Investment Suggestions")
199
+ for tip in spending_suggestions(df, profile):
200
+ st.write("•", tip)
201
 
202
+ with col_chat:
203
+ st.subheader("🗨️ Ask your finance question")
204
+ for turn in st.session_state.chat_history:
205
+ with st.chat_message(turn["role"]):
206
+ st.markdown(turn["content"])
207
+ user_msg = st.chat_input("Type your finance/numbers-related question…")
208
+ if user_msg:
209
+ # PREVENT OFF-TOPIC
210
+ if not is_finance_related(user_msg):
211
+ assistant_message = "Sorry, I can only answer questions related to finance or numbers. Please rephrase your query."
212
+ st.session_state.chat_history.append({"role": "assistant", "content": assistant_message})
213
+ with st.chat_message("assistant"):
214
+ st.markdown(assistant_message)
215
  else:
216
+ st.session_state.chat_history.append({"role": "user", "content": user_msg})
217
+ # Demographic-aware + context-aware system prompt
218
+ sys_prompt = (
219
+ f"You are a finance-focused AI chatbot, expert in Indian personal finance. "
220
+ f"User: {profile.user_type}, Age {profile.age}, Location {profile.country}, "
221
+ f"Monthly Income ₹{profile.monthly_income:.0f}, Risk Tolerance {profile.risk}, Goals: {profile.goals}. "
222
+ f"{profile.style_prompt()} "
223
+ "Do NOT answer non-finance queries. Always use friendly, supportive, and context-aware explanations."
224
+ )
225
+ context = (
226
+ f"Context: User's Current Budget - Income ₹{summary['income_total']}, "
227
+ f"Expenses ₹{summary['expense_total']}, Net ₹{summary['net_savings']}, "
228
+ f"Savings Rate {summary['savings_rate_pct']}%."
229
+ )
230
+ user_prompt = (
231
+ f"{context}\nUser asked: {user_msg}\n"
232
+ "Split your answer into: 1) Quick answer, 2) Why it matters, 3) Next steps (bullets), 4) Caution notes."
233
+ )
234
+ full_prompt = sys_prompt + "\n\n" + user_prompt
235
+ with st.chat_message("assistant"):
236
+ with st.spinner(f"Thinking with {provider.name}…"):
237
+ try:
238
+ ai = provider.generate(full_prompt, max_tokens=768)
239
+ except Exception as e:
240
+ ai = f"Provider error: {e}\nFallback: Use only rule-based advice."
241
+ st.markdown(ai)
242
+ st.session_state.chat_history.append({"role": "assistant", "content": ai})
243
 
244
+ st.markdown("""
245
+ ---
246
+ **Disclaimer:** This chatbot provides educational information only and is _not_ financial, tax, or legal advice.
247
+ Consult a licensed professional for tailored guidance. Tax laws and investment products change frequently.
248
+ """)