Rahul2298 commited on
Commit
ea1c363
·
verified ·
1 Parent(s): e82012c

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +101 -29
src/streamlit_app.py CHANGED
@@ -6,17 +6,21 @@ 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:
@@ -26,13 +30,34 @@ if "provider_inited" not in st.session_state:
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__()
@@ -44,16 +69,20 @@ class HuggingFaceProvider(AIProvider):
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 rulebased 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,18 +92,19 @@ class IBMGraniteWatsonProvider(AIProvider):
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():
@@ -82,6 +112,7 @@ def categorize(desc: str) -> str:
82
  return cat
83
  return "other"
84
 
 
85
  @dataclass
86
  class UserProfile:
87
  name: str
@@ -91,32 +122,42 @@ class UserProfile:
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 lowjargon."
99
  )
100
  return (
101
  "Explain like a professional financial coach. Be precise, structured, and include "
102
- "brief rationale with tradeoffs."
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()
@@ -136,12 +177,20 @@ def budget_summary(df: pd.DataFrame, monthly_income_hint: Optional[float] = None
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()
@@ -150,22 +199,40 @@ def detect_intent(text: str) -> str:
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
- pass
158
- # ... unchanged
159
 
 
 
 
160
  with st.sidebar:
161
- # Inputs (unchanged)
162
- # ...
163
-
164
- profile = UserProfile(
165
- # ... unchanged
166
- )
 
 
 
 
 
 
 
 
 
167
 
168
- # Provider initialization block
 
 
169
  if not st.session_state["provider_inited"]:
170
  st.session_state["providers"]["ibm"] = IBMGraniteWatsonProvider(
171
  watson_api_key=os.getenv("IBM_WATSON_API_KEY"),
@@ -173,7 +240,7 @@ if not st.session_state["provider_inited"]:
173
  granite_key=os.getenv("IBM_GRANITE_API_KEY"),
174
  )
175
  st.session_state["providers"]["hf"] = HuggingFaceProvider()
176
- # Choose provider based on user choice and credentials
177
  chosen = "huggingface"
178
  ibm_ok = st.session_state["providers"]["ibm"].ok
179
  if provider_choice.startswith("IBM") and ibm_ok:
@@ -183,7 +250,6 @@ if not st.session_state["provider_inited"]:
183
  st.session_state["provider_name"] = chosen
184
  st.session_state["provider_inited"] = True
185
 
186
- # Always pick the right provider, even after sidebar changes
187
  provider_name = st.session_state["provider_name"]
188
  if provider_choice.startswith("IBM"):
189
  provider_name = "ibm_granite_watson" if st.session_state["providers"]["ibm"].ok else "huggingface"
@@ -192,12 +258,17 @@ elif provider_choice.startswith("Auto"):
192
  else:
193
  provider_name = "huggingface"
194
  st.session_state["provider_name"] = provider_name
 
195
  provider = (
196
  st.session_state["providers"]["ibm"] if provider_name == "ibm_granite_watson"
197
  else st.session_state["providers"]["hf"]
198
  )
199
 
 
 
 
200
  col_chat, col_right = st.columns([0.62, 0.38])
 
201
  with col_right:
202
  st.subheader("📊 Budget Summary")
203
  df = load_transactions(uploaded)
@@ -208,6 +279,7 @@ with col_right:
208
  m2.metric("Expenses (₹)", f"{summary['expense_total']:.0f}")
209
  m3.metric("Net (₹)", f"{summary['net_savings']:.0f}")
210
  m4.metric("Savings Rate", f"{summary['savings_rate_pct']:.1f}%")
 
211
  st.markdown("### 🧠 AI Spending Suggestions")
212
  for tip in spending_suggestions(df, profile):
213
  st.write("• ", tip)
@@ -238,4 +310,4 @@ st.markdown("""
238
  **Disclaimers**
239
  This chatbot provides educational information only and is **not** financial, tax, or legal advice.
240
  Tax rules change frequently; consult a qualified professional for personalized advice.
241
- """)
 
6
  import pandas as pd
7
  import streamlit as st
8
 
9
+ # Hugging Face (optional)
10
  try:
11
  from transformers import pipeline
12
  HF_AVAILABLE = True
13
  except Exception:
14
  HF_AVAILABLE = False
15
 
16
+ # -------------------------
17
+ # Streamlit Config
18
+ # -------------------------
19
  st.set_page_config(page_title="Personal Finance Chatbot", page_icon="💬", layout="wide")
20
 
21
+ # -------------------------
22
+ # Session State Init
23
+ # -------------------------
24
  if "chat_history" not in st.session_state:
25
  st.session_state["chat_history"] = []
26
  if "providers" not in st.session_state:
 
30
  if "provider_name" not in st.session_state:
31
  st.session_state["provider_name"] = "huggingface"
32
 
33
+ # -------------------------
34
+ # Categories & Intent Patterns
35
+ # -------------------------
36
+ CATEGORIES = {
37
+ "food": ["grocery", "restaurant", "dining"],
38
+ "transport": ["bus", "metro", "uber", "ola", "taxi"],
39
+ "entertainment": ["movie", "netflix", "spotify"],
40
+ "other": []
41
+ }
42
 
43
+ INTENT_PATTERNS = {
44
+ "savings": r"\bsav(e|ings|ing)\b",
45
+ "tax": r"\btax(es)?\b",
46
+ "invest": r"\binvest(ment|ing)?\b",
47
+ "budget": r"\bbudget(ing)?\b"
48
+ }
49
+
50
+ # -------------------------
51
+ # AI Providers
52
+ # -------------------------
53
  class AIProvider:
54
  def __init__(self):
55
  self.name = "base"
56
+
57
  def generate(self, prompt: str, max_tokens: int = 512) -> str:
58
  raise NotImplementedError
59
 
60
+
61
  class HuggingFaceProvider(AIProvider):
62
  def __init__(self):
63
  super().__init__()
 
69
  except Exception as e:
70
  st.warning(f"HuggingFace pipeline failed to load: {e}")
71
  else:
72
+ st.info("Transformers not installed; responses will be rule-based only.")
73
+
74
  def generate(self, prompt: str, max_tokens: int = 512) -> str:
75
  if self.gen is None:
76
  return (
77
  "[Rule-based fallback]\n"
78
  + prompt[:1000]
79
+ + "\n\n(Summarized suggestion) Consider tracking expenses, setting goals, "
80
+ "building an emergency fund, and using diversified, low-cost index funds aligned "
81
+ "with your risk tolerance.)"
82
  )
83
  out = self.gen(prompt, max_length=min(1024, max_tokens), do_sample=False)
84
+ return out[0]["generated_text"].strip()
85
+
86
 
87
  class IBMGraniteWatsonProvider(AIProvider):
88
  def __init__(self, watson_api_key: Optional[str], watson_url: Optional[str], granite_key: Optional[str]):
 
92
  self.watson_api_key = watson_api_key
93
  self.watson_url = watson_url
94
  self.granite_key = granite_key
95
+
96
  def generate(self, prompt: str, max_tokens: int = 512) -> str:
97
  if not self.ok:
98
  return "[IBM placeholder] Missing credentials — falling back text.\n" + prompt
 
99
  return (
100
  "[IBM Granite/Watson simulated response]\n"
101
  "(Replace this with real SDK call)\n\n"
102
  + prompt
103
  )
104
 
105
+ # -------------------------
106
+ # Helpers
107
+ # -------------------------
108
  def categorize(desc: str) -> str:
109
  desc_l = (desc or "").lower()
110
  for cat, keys in CATEGORIES.items():
 
112
  return cat
113
  return "other"
114
 
115
+
116
  @dataclass
117
  class UserProfile:
118
  name: str
 
122
  monthly_income: float
123
  risk_tolerance: str
124
  goals: str
125
+
126
  def style_prompt(self) -> str:
127
  if self.user_type.lower().startswith("stud"):
128
  return (
129
  "Explain like a friendly mentor to a student. Keep it clear and concise, "
130
+ "use practical examples and low-jargon."
131
  )
132
  return (
133
  "Explain like a professional financial coach. Be precise, structured, and include "
134
+ "brief rationale with trade-offs."
135
  )
136
 
137
+
138
  def load_transactions(uploaded_file: Optional[io.BytesIO]) -> pd.DataFrame:
139
  if uploaded_file is None:
140
+ data = {
141
+ "date": ["2025-08-01", "2025-08-02", "2025-08-03"],
142
+ "description": ["grocery store", "uber ride", "netflix subscription"],
143
+ "amount": [-1500, -300, -5000]
144
+ }
145
  df = pd.DataFrame(data)
146
  else:
147
  try:
148
  df = pd.read_csv(uploaded_file)
149
  except Exception as e:
150
  st.error(f"Could not read CSV: {e}. Showing demo data.")
151
+ data = {
152
+ "date": ["2025-08-01", "2025-08-02", "2025-08-03"],
153
+ "description": ["grocery store", "uber ride", "netflix subscription"],
154
+ "amount": [-1500, -300, -5000]
155
+ }
156
  df = pd.DataFrame(data)
157
  df["category"] = df["description"].apply(categorize)
158
  return df
159
 
160
+
161
  def budget_summary(df: pd.DataFrame, monthly_income_hint: Optional[float] = None) -> Dict[str, float]:
162
  df["month"] = pd.to_datetime(df["date"]).dt.to_period("M").astype(str)
163
  income = df.loc[df["amount"] > 0, "amount"].sum()
 
177
  "top_spend_json": top_spend.to_json(),
178
  }
179
 
180
+
181
  def spending_suggestions(df: pd.DataFrame, profile: UserProfile) -> List[str]:
182
  tips = []
183
  summary = budget_summary(df, monthly_income_hint=profile.monthly_income)
184
+ if summary["savings_rate_pct"] < 10:
185
+ tips.append("Your savings rate is low. Try setting aside at least 20% of your income.")
186
+ if "entertainment" in df["category"].values:
187
+ tips.append("Entertainment spending is high. Consider limiting subscriptions or outings.")
188
+ if "food" in df["category"].values:
189
+ tips.append("Track food expenses. Cooking at home can save money.")
190
+ if not tips:
191
+ tips.append("Good job! Your spending looks balanced.")
192
+ return tips
193
 
 
194
 
195
  def detect_intent(text: str) -> str:
196
  t = text.lower()
 
199
  return k
200
  return "general"
201
 
202
+
203
  def build_system_prompt(profile: UserProfile) -> str:
204
+ return (
205
+ f"You are a financial assistant. User profile: {profile}. "
206
+ f"Respond in style: {profile.style_prompt()}"
207
+ )
208
+
209
 
210
  def craft_user_prompt(query: str, intent: str, summary: Dict[str, float]) -> str:
211
+ return f"User asked about {intent}: {query}\nBudget summary: {summary}"
 
212
 
213
+ # -------------------------
214
+ # Sidebar Inputs
215
+ # -------------------------
216
  with st.sidebar:
217
+ provider_choice = st.radio(
218
+ "Select AI Provider",
219
+ ["HuggingFace", "IBM Granite Watson", "Auto (Best Available)"]
220
+ )
221
+ uploaded = st.file_uploader("Upload your transactions CSV", type=["csv"])
222
+ st.markdown("### Profile")
223
+ profile = UserProfile(
224
+ name=st.text_input("Name", "Rahul"),
225
+ user_type=st.selectbox("User Type", ["Student", "Professional"]),
226
+ age=st.number_input("Age", 18, 100, 25),
227
+ country=st.text_input("Country", "India"),
228
+ monthly_income=st.number_input("Monthly Income", 0.0, 1e7, 50000.0),
229
+ risk_tolerance=st.selectbox("Risk Tolerance", ["Low", "Medium", "High"]),
230
+ goals=st.text_area("Financial Goals", "Save for emergency fund and invest in mutual funds")
231
+ )
232
 
233
+ # -------------------------
234
+ # Provider Initialization
235
+ # -------------------------
236
  if not st.session_state["provider_inited"]:
237
  st.session_state["providers"]["ibm"] = IBMGraniteWatsonProvider(
238
  watson_api_key=os.getenv("IBM_WATSON_API_KEY"),
 
240
  granite_key=os.getenv("IBM_GRANITE_API_KEY"),
241
  )
242
  st.session_state["providers"]["hf"] = HuggingFaceProvider()
243
+
244
  chosen = "huggingface"
245
  ibm_ok = st.session_state["providers"]["ibm"].ok
246
  if provider_choice.startswith("IBM") and ibm_ok:
 
250
  st.session_state["provider_name"] = chosen
251
  st.session_state["provider_inited"] = True
252
 
 
253
  provider_name = st.session_state["provider_name"]
254
  if provider_choice.startswith("IBM"):
255
  provider_name = "ibm_granite_watson" if st.session_state["providers"]["ibm"].ok else "huggingface"
 
258
  else:
259
  provider_name = "huggingface"
260
  st.session_state["provider_name"] = provider_name
261
+
262
  provider = (
263
  st.session_state["providers"]["ibm"] if provider_name == "ibm_granite_watson"
264
  else st.session_state["providers"]["hf"]
265
  )
266
 
267
+ # -------------------------
268
+ # Layout
269
+ # -------------------------
270
  col_chat, col_right = st.columns([0.62, 0.38])
271
+
272
  with col_right:
273
  st.subheader("📊 Budget Summary")
274
  df = load_transactions(uploaded)
 
279
  m2.metric("Expenses (₹)", f"{summary['expense_total']:.0f}")
280
  m3.metric("Net (₹)", f"{summary['net_savings']:.0f}")
281
  m4.metric("Savings Rate", f"{summary['savings_rate_pct']:.1f}%")
282
+
283
  st.markdown("### 🧠 AI Spending Suggestions")
284
  for tip in spending_suggestions(df, profile):
285
  st.write("• ", tip)
 
310
  **Disclaimers**
311
  This chatbot provides educational information only and is **not** financial, tax, or legal advice.
312
  Tax rules change frequently; consult a qualified professional for personalized advice.
313
+ """)