Navya-Sree commited on
Commit
e14e905
·
verified ·
1 Parent(s): 2283865

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +524 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,526 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ from datetime import date
3
+ import random
4
 
5
+ from llm_utils import explain_savings_plan
6
+
7
+
8
+ # ---------------------- PAGE CONFIG ----------------------
9
+ st.set_page_config(
10
+ page_title="Pistol Pete SmartAgent",
11
+ page_icon="🏠",
12
+ layout="centered",
13
+ )
14
+
15
+
16
+ # ---------------------- STATE INIT -----------------------
17
+ def init_state():
18
+ if "step" not in st.session_state:
19
+ st.session_state.step = 1
20
+
21
+ st.session_state.setdefault("home_type", "")
22
+ st.session_state.setdefault("location", "")
23
+ st.session_state.setdefault("timeline_bucket", 3) # 1–5 slider
24
+ st.session_state.setdefault("budget", 350000)
25
+
26
+ st.session_state.setdefault("annual_income", "75000")
27
+ st.session_state.setdefault("monthly_debt", "350")
28
+ st.session_state.setdefault("current_savings", "12000")
29
+
30
+ # Controls on Screen 5
31
+ st.session_state.setdefault("timeline_years_plan", 4) # 2–7 years
32
+ st.session_state.setdefault("down_pct", 20) # 10–20
33
+
34
+ # LLM explanation cache
35
+ st.session_state.setdefault("llm_explanation", None)
36
+
37
+
38
+ def to_int(value, default):
39
+ try:
40
+ if value is None:
41
+ return default
42
+ if isinstance(value, (int, float)):
43
+ return int(value)
44
+ v = str(value).replace(",", "").strip()
45
+ if v == "":
46
+ return default
47
+ return int(v)
48
+ except Exception:
49
+ return default
50
+
51
+
52
+ init_state()
53
+
54
+
55
+ # ---------------------- GOVERNANCE SIDEBAR ----------------------
56
+ with st.sidebar:
57
+ st.markdown("### 🔐 Governance & Fairness")
58
+ st.caption(
59
+ "- No protected attributes (race, gender, etc.) are used in calculations.\n"
60
+ "- The LLM **only** explains numbers already computed.\n"
61
+ "- Users can adjust timeline & budget and keep full control.\n"
62
+ "- Prototype only — no real PII or accounts."
63
+ )
64
+
65
+
66
+ # ---------------------- HELPERS ----------------------
67
+ def compute_savings_plan(budget, down_pct, current_savings, timeline_years):
68
+ """Port of the JS formula for monthly savings & target date."""
69
+ down_payment_amount = round(budget * (down_pct / 100))
70
+ remaining_need = max(0, down_payment_amount - current_savings)
71
+ months = max(1, timeline_years * 12)
72
+
73
+ monthly_base = remaining_need / months
74
+ closing_costs = 5000 / months
75
+
76
+ # interestEarnings approx like JS: 1.5% APY on (savings + contributions)
77
+ annual_rate = 0.015
78
+ balance = current_savings
79
+ total_interest = 0.0
80
+ for _ in range(timeline_years):
81
+ balance += monthly_base * 12
82
+ annual_interest = balance * annual_rate
83
+ total_interest += annual_interest
84
+ balance += annual_interest
85
+ interest_earnings = total_interest / months
86
+
87
+ monthly_savings = monthly_base + closing_costs - interest_earnings
88
+
89
+ today = date.today()
90
+ target_year = today.year + timeline_years
91
+ target_date = date(target_year, today.month, today.day)
92
+
93
+ return {
94
+ "down_payment_amount": down_payment_amount,
95
+ "remaining_need": round(remaining_need),
96
+ "months": months,
97
+ "monthly_base": monthly_base,
98
+ "closing_costs": closing_costs,
99
+ "interest_earnings": interest_earnings,
100
+ "monthly_savings": monthly_savings,
101
+ "target_date": target_date,
102
+ }
103
+
104
+
105
+ def compute_estimated_home_value(budget, timeline_years, location, down_pct):
106
+ """
107
+ Approx of estimateValue():
108
+ - baseline = budget
109
+ - adjust by ((years - 4) * 0.02)
110
+ - random ± 15000
111
+ - small location tweak
112
+ """
113
+ base_budget = budget
114
+ est = base_budget
115
+
116
+ rand_adj = random.randint(-15000, 15000)
117
+ timeline_adj_pct = (timeline_years - 4) * 0.02
118
+ est = round(est * (1 + timeline_adj_pct) + rand_adj)
119
+
120
+ loc = (location or "").lower()
121
+ if "pittsburgh" in loc or "lawrenceville" in loc:
122
+ est = round(est * 0.98)
123
+
124
+ if abs(rand_adj) < 6000 and timeline_years <= 5:
125
+ confidence = "High"
126
+ elif abs(rand_adj) < 12000:
127
+ confidence = "Moderate"
128
+ else:
129
+ confidence = "Low"
130
+
131
+ return est, confidence
132
+
133
+
134
+ def next_step():
135
+ st.session_state.step = min(7, st.session_state.step + 1)
136
+
137
+
138
+ def prev_step():
139
+ st.session_state.step = max(1, st.session_state.step - 1)
140
+
141
+
142
+ # ---------------------- PROGRESS BAR ----------------------
143
+ st.markdown(
144
+ "<h2 style='text-align:center;'>🏠 Pistol Pete SmartAgent</h2>",
145
+ unsafe_allow_html=True,
146
+ )
147
+ st.caption("Trusted, explainable AI coach for first-home savings (CSL 2025 prototype).")
148
+ st.progress(st.session_state.step / 7.0)
149
+ st.divider()
150
+
151
+ step = st.session_state.step
152
+
153
+
154
+ # ---------------------- SCREEN 1 ----------------------
155
+ if step == 1:
156
+ st.header("Ready to Plan for Your Home?")
157
+ st.write(
158
+ "Our SmartAgent helps you create a personalized savings roadmap. "
159
+ "You're in control every step of the way with transparent, explainable recommendations."
160
+ )
161
+
162
+ with st.expander("How it works", expanded=True):
163
+ st.markdown(
164
+ "- We ask about your **home goals** and **finances**.\n"
165
+ "- We create a **down-payment savings plan**.\n"
166
+ "- You can see the **exact math** and adjust your own timeline and budget.\n"
167
+ "- An **LLM explainer** turns the numbers into plain-English explanations."
168
+ )
169
+
170
+ st.button("Start Your Personalized Plan ➜", on_click=next_step)
171
+
172
+
173
+ # ---------------------- SCREEN 2 ----------------------
174
+ elif step == 2:
175
+ st.header("Let's Build Your Home Plan Together")
176
+ st.write("Tell us about your dream home — this helps us create a plan that's right for you.")
177
+
178
+ st.session_state.home_type = st.radio(
179
+ "What type of home are you dreaming of?",
180
+ ["Single-Family Home", "Townhouse", "Condominium", "I'm not sure yet"],
181
+ index=3
182
+ if st.session_state.home_type == ""
183
+ else ["Single-Family Home", "Townhouse", "Condominium", "I'm not sure yet"].index(
184
+ st.session_state.home_type
185
+ ),
186
+ )
187
+
188
+ st.session_state.location = st.text_input(
189
+ "Where are you thinking of buying?",
190
+ value=st.session_state.location,
191
+ placeholder="e.g., Pittsburgh, PA – Lawrenceville",
192
+ )
193
+
194
+ col1, col2 = st.columns(2)
195
+ with col1:
196
+ st.button("⬅ Back", on_click=prev_step, key="back_2")
197
+ with col2:
198
+ st.button("Continue ➜", on_click=next_step, key="next_2")
199
+
200
+
201
+ # ---------------------- SCREEN 3 ----------------------
202
+ elif step == 3:
203
+ st.header("Your Timeline & Budget")
204
+ st.write("Now let's talk about when you'd like to buy and your comfortable budget range.")
205
+
206
+ st.session_state.timeline_bucket = st.slider(
207
+ "What's your ideal timeline to buy?",
208
+ min_value=1,
209
+ max_value=5,
210
+ value=st.session_state.timeline_bucket,
211
+ )
212
+
213
+ def bucket_label(b):
214
+ return {
215
+ 1: "< 1 year",
216
+ 2: "1–2 years",
217
+ 3: "3–5 years",
218
+ 4: "5–7 years",
219
+ 5: "7+ years",
220
+ }[b]
221
+
222
+ st.caption(f"Selected range: **{bucket_label(st.session_state.timeline_bucket)}**")
223
+
224
+ home_budget_raw = st.text_input(
225
+ "What is your comfortable estimated home budget?",
226
+ value=str(st.session_state.budget),
227
+ placeholder="e.g., 350000",
228
+ )
229
+ st.session_state.budget = to_int(home_budget_raw, st.session_state.budget)
230
+
231
+ if st.session_state.location and any(
232
+ k in st.session_state.location.lower() for k in ["pittsburgh", "lawrenceville"]
233
+ ):
234
+ st.info("We see average prices in Lawrenceville are ~ $350,000 (illustrative).")
235
+
236
+ st.checkbox(
237
+ "Let our AI calculate a budget for me based on my finances (concept only)",
238
+ value=False,
239
+ help="For this prototype, you still control the budget.",
240
+ )
241
+
242
+ col1, col2 = st.columns(2)
243
+ with col1:
244
+ st.button("⬅ Back", on_click=prev_step, key="back_3")
245
+ with col2:
246
+ st.button("Continue ➜", on_click=next_step, key="next_3")
247
+
248
+
249
+ # ---------------------- SCREEN 4 ----------------------
250
+ elif step == 4:
251
+ st.header("Your Financial Snapshot")
252
+ st.write(
253
+ "To provide the most accurate plan, we’ll use the financial info you've shared. "
254
+ "Verify or update any of the details below."
255
+ )
256
+
257
+ st.info("We value your privacy. This information is used only to create your plan.")
258
+
259
+ st.session_state.annual_income = st.text_input(
260
+ "Annual Gross Income",
261
+ value=st.session_state.annual_income,
262
+ help="Example: sourced from bank deposits",
263
+ )
264
+
265
+ st.session_state.monthly_debt = st.text_input(
266
+ "Monthly Debt Payments",
267
+ value=st.session_state.monthly_debt,
268
+ help="Example: credit cards, loans",
269
+ )
270
+
271
+ st.session_state.current_savings = st.text_input(
272
+ "Current Down Payment Savings",
273
+ value=st.session_state.current_savings,
274
+ help="Example: savings accounts earmarked for housing",
275
+ )
276
+
277
+ col1, col2 = st.columns(2)
278
+ with col1:
279
+ st.button("⬅ Back", on_click=prev_step, key="back_4")
280
+ with col2:
281
+ st.button("Generate My Home Plan ➜", on_click=next_step, key="next_4")
282
+
283
+
284
+ # ---------------------- SCREEN 5 ----------------------
285
+ elif step == 5:
286
+ st.header("Your Personalized Home Plan")
287
+
288
+ location_label = st.session_state.location or "your target area"
289
+ st.write(
290
+ f"Based on your goals for a home in **{location_label}**, "
291
+ "here's your tailored savings roadmap."
292
+ )
293
+
294
+ budget = st.session_state.budget
295
+ income = to_int(st.session_state.annual_income, 75000)
296
+ debt = to_int(st.session_state.monthly_debt, 350)
297
+ savings = to_int(st.session_state.current_savings, 12000)
298
+
299
+ # ----- Adjust Your Plan -----
300
+ st.subheader("Adjust Your Plan")
301
+
302
+ colA, colB = st.columns(2)
303
+ with colA:
304
+ st.session_state.timeline_years_plan = st.slider(
305
+ "Move Your Timeline (years)",
306
+ min_value=2,
307
+ max_value=7,
308
+ value=st.session_state.timeline_years_plan,
309
+ )
310
+ with colB:
311
+ st.session_state.budget = st.slider(
312
+ "Adjust Home Budget ($)",
313
+ min_value=250000,
314
+ max_value=600000,
315
+ step=50000,
316
+ value=st.session_state.budget,
317
+ )
318
+
319
+ st.session_state.down_pct = st.slider(
320
+ "Change Down Payment %",
321
+ min_value=10,
322
+ max_value=20,
323
+ step=5,
324
+ value=st.session_state.down_pct,
325
+ )
326
+
327
+ if st.session_state.down_pct < 20:
328
+ st.warning("A down payment below 20% may require Private Mortgage Insurance (PMI).")
329
+
330
+ budget = st.session_state.budget
331
+ plan = compute_savings_plan(
332
+ budget=budget,
333
+ down_pct=st.session_state.down_pct,
334
+ current_savings=savings,
335
+ timeline_years=st.session_state.timeline_years_plan,
336
+ )
337
+
338
+ # ----- Savings Timeline -----
339
+ st.subheader("Savings Timeline")
340
+ col_today, _, col_goal = st.columns([1, 2, 1])
341
+ with col_today:
342
+ st.caption("Today")
343
+ st.markdown(f"**${savings:,.0f}**")
344
+ with col_goal:
345
+ st.caption("Goal")
346
+ st.markdown(f"**${plan['down_payment_amount']:,.0f}**")
347
+
348
+ st.caption(
349
+ f"Target Date: **{plan['target_date'].strftime('%B %Y')}** "
350
+ f"({st.session_state.timeline_years_plan} years)"
351
+ )
352
+
353
+ st.divider()
354
+
355
+ # ----- AI Recommendation -----
356
+ st.subheader("AI Recommendation")
357
+ st.write(
358
+ f"To reach your goal in **{st.session_state.timeline_years_plan} years**, "
359
+ "we recommend saving:"
360
+ )
361
+ st.markdown(
362
+ f"<div style='font-size:32px;font-weight:800;color:#D96932;text-align:center;'>"
363
+ f"${plan['monthly_savings']:,.0f}/month</div>",
364
+ unsafe_allow_html=True,
365
+ )
366
+ st.caption("Confidence: **High** (demo).")
367
+
368
+ st.write(
369
+ "💡 **Why this level?** Your income and debts produce a debt-to-income profile that "
370
+ "supports this plan, plus predictable assumptions for this prototype."
371
+ )
372
+
373
+ # ----- LLM Explanation Layer -----
374
+ st.markdown("#### Explanation (AI Coach)")
375
+
376
+ payload = {
377
+ "home_budget": budget,
378
+ "down_payment_percent": st.session_state.down_pct,
379
+ "down_payment_amount": plan["down_payment_amount"],
380
+ "current_savings": savings,
381
+ "remaining_need": plan["remaining_need"],
382
+ "timeline_years": st.session_state.timeline_years_plan,
383
+ "timeline_months": plan["months"],
384
+ "recommended_monthly_savings": round(plan["monthly_savings"]),
385
+ "annual_income": income,
386
+ "monthly_debt": debt,
387
+ }
388
+
389
+ if st.button("Explain this plan with AI"):
390
+ with st.spinner("Generating a plain-English explanation..."):
391
+ explanation = explain_savings_plan(payload)
392
+ st.session_state.llm_explanation = explanation
393
+
394
+ if st.session_state.llm_explanation:
395
+ st.info(st.session_state.llm_explanation)
396
+ else:
397
+ st.caption("Click the button above to see the AI Coach explain your plan in simple terms.")
398
+
399
+ # ----- See the AI's Math -----
400
+ with st.expander("See the AI's Math"):
401
+ st.write(
402
+ f"**Target Down Payment ({st.session_state.down_pct}%):** "
403
+ f"${plan['down_payment_amount']:,.0f}"
404
+ )
405
+ st.write(f"**− Current Savings:** ${savings:,.0f}")
406
+ st.write(f"**= Remaining Need:** ${plan['remaining_need']:,.0f}")
407
+ st.write(
408
+ f"**÷ Months in Timeline ({plan['months']}):** "
409
+ f"${plan['monthly_base']:,.0f} base / month"
410
+ )
411
+ st.write(
412
+ f"**+ Estimated Closing Costs (≈ $5,000 / {plan['months']}):** "
413
+ f"+ ${plan['closing_costs']:,.0f}"
414
+ )
415
+ st.write(
416
+ f"**− Projected Interest on Savings (@1.5% APY):** "
417
+ f"− ${plan['interest_earnings']:,.0f}"
418
+ )
419
+ st.write(
420
+ f"**= Recommended Monthly Savings:** "
421
+ f"${plan['monthly_savings']:,.0f} / month"
422
+ )
423
+
424
+ st.divider()
425
+
426
+ col1, col2 = st.columns(2)
427
+ with col1:
428
+ st.button("⬅ Back", on_click=prev_step, key="back_5")
429
+ with col2:
430
+ st.button("Next Steps ➜", on_click=next_step, key="next_5")
431
+
432
+
433
+ # ---------------------- SCREEN 6 ----------------------
434
+ elif step == 6:
435
+ st.header("Tools to Help You Succeed")
436
+ st.write("Based on your plan, here are products and support options to accelerate progress.")
437
+
438
+ savings = to_int(st.session_state.current_savings, 12000)
439
+
440
+ st.subheader("High-Yield “Home Fund” Savings Account (Concept)")
441
+ st.write(
442
+ f"With **${savings:,.0f}** already saved, a dedicated high-yield 'Home Fund' account "
443
+ "could earn more interest compared to a basic account."
444
+ )
445
+ st.caption("Why we suggest this: to maximize your savings growth with minimal risk.")
446
+
447
+ st.divider()
448
+
449
+ st.subheader("Home Loan Pre-approval (Concept)")
450
+ st.write(
451
+ "A pre-approval helps clarify your budget and shows sellers you're serious. "
452
+ "Many successful buyers get pre-approved early."
453
+ )
454
+ st.caption("Why we suggest this: to reduce uncertainty and strengthen your offers.")
455
+
456
+ st.divider()
457
+
458
+ st.subheader("Prefer to Speak with a Human Agent?")
459
+ with st.form("contact_form"):
460
+ name = st.text_input("Full name")
461
+ email = st.text_input("Email address")
462
+ phone = st.text_input("Phone (optional)")
463
+ msg = st.text_area("How can we assist you?")
464
+ submitted = st.form_submit_button("Send Message")
465
+ if submitted:
466
+ st.success(
467
+ f"Thanks {name or 'there'}! A human agent would contact you at "
468
+ f"{email or phone or 'your provided contact'} in a real deployment."
469
+ )
470
+
471
+ st.divider()
472
+ col1, col2 = st.columns(2)
473
+ with col1:
474
+ st.button("⬅ Back to Plan", on_click=prev_step, key="back_6")
475
+ with col2:
476
+ st.button("Save & Estimate Value ➜", on_click=next_step, key="next_6")
477
+
478
+
479
+ # ---------------------- SCREEN 7 ----------------------
480
+ elif step == 7:
481
+ st.header("Your Estimated Home Value")
482
+
483
+ budget = st.session_state.budget
484
+ down_pct = st.session_state.down_pct
485
+ timeline_years = st.session_state.timeline_years_plan
486
+ location = st.session_state.location
487
+
488
+ est_value, confidence = compute_estimated_home_value(
489
+ budget=budget,
490
+ timeline_years=timeline_years,
491
+ location=location,
492
+ down_pct=down_pct,
493
+ )
494
+
495
+ st.markdown(
496
+ f"<div style='background:#FFF8EC;border-radius:12px;padding:18px;"
497
+ f"box-shadow:0 6px 18px rgba(217,105,50,0.08);text-align:center;'>"
498
+ f"<h2 style='font-size:34px;color:#D96932;'>${est_value:,.0f}</h2>"
499
+ f"<p>Estimated probable home value given your inputs (illustrative only).</p>"
500
+ f"<p>Confidence: <strong>{confidence}</strong></p>"
501
+ f"</div>",
502
+ unsafe_allow_html=True,
503
+ )
504
+
505
+ st.subheader("How we estimate")
506
+ st.write(
507
+ "We combine your budget, timeline, and a simple heuristic to produce a probable value. "
508
+ "In production, MLS or public-record APIs would provide more accurate estimates."
509
+ )
510
+
511
+ colA, colB = st.columns(2)
512
+ with colA:
513
+ st.markdown("**Input Budget**")
514
+ st.markdown(f"${budget:,.0f}")
515
+ with colB:
516
+ st.markdown("**Down Payment (approx)**")
517
+ st.markdown(f"${round(budget * down_pct / 100):,.0f}")
518
+
519
+ st.divider()
520
+
521
+ col1, col2 = st.columns(2)
522
+ with col1:
523
+ st.button("⬅ Back", on_click=prev_step, key="back_7")
524
+ with col2:
525
+ if st.button("Finish & Save"):
526
+ st.success("Your plan summary has been saved (demo only). Refresh to restart.")