cherrisai/wealth_AI
1
1"""2====================================================3 AI Personal Wealth Analyzer — Streamlit App4 Run: streamlit run app.py5 Requires: finance_model.pkl in same folder6====================================================7"""8 9import streamlit as st10import numpy as np11import pandas as pd12import pickle13import plotly.express as px14import plotly.graph_objects as go15from fpdf import FPDF16import time17import json18import os19from datetime import datetime20 21# ============================================================22# PAGE CONFIG23# ============================================================24st.set_page_config(25 page_title="AI Personal Wealth Analyzer",26 layout="wide"27)28 29# ============================================================30# GLOBAL CSS31# ============================================================32st.markdown("""33<style>34.section-title {35 background: linear-gradient(90deg, #0f2027, #2c5364);36 color: white;37 padding: 10px 20px;38 border-radius: 10px;39 font-size: 20px;40 font-weight: bold;41 margin-bottom: 15px;42 letter-spacing: 0.5px;43}44.kpi {45 background: linear-gradient(135deg, #2c5364, #0f2027);46 padding: 25px;47 border-radius: 15px;48 color: white;49 text-align: center;50 font-size: 22px;51 font-weight: bold;52 box-shadow: 0px 4px 15px rgba(0,0,0,0.3);53}54.info-card {55 background: #1e293b;56 border-left: 5px solid #38bdf8;57 padding: 15px 20px;58 border-radius: 8px;59 color: #e2e8f0;60 margin: 8px 0;61 font-size: 15px;62}63.loan-card {64 background: #0f172a;65 border: 1px solid #334155;66 padding: 12px 18px;67 border-radius: 10px;68 color: #cbd5e1;69 margin: 5px 0;70 font-size: 14px;71}72.pred-card {73 background: #0f172a;74 border: 1px solid #38bdf8;75 padding: 14px 18px;76 border-radius: 10px;77 color: #e2e8f0;78 margin: 6px 0;79 font-size: 14px;80}81.alert-gain {82 background: #052e16;83 border-left: 5px solid #22c55e;84 padding: 14px 18px;85 border-radius: 10px;86 color: #bbf7d0;87 margin: 6px 0;88 font-size: 15px;89}90.alert-loss {91 background: #2d0a0a;92 border-left: 5px solid #ef4444;93 padding: 14px 18px;94 border-radius: 10px;95 color: #fecaca;96 margin: 6px 0;97 font-size: 15px;98}99</style>100""", unsafe_allow_html=True)101 102 103# ============================================================104# HELPER — renders a styled section title105# ============================================================106def section(icon, title):107 st.markdown(f'<div class="section-title">{icon} {title}</div>', unsafe_allow_html=True)108 109 110# ============================================================111# SMART RUPEE FORMATTER112# Format: K = thousands, L = lakhs, Cr = crores113# ============================================================114def fmt(amount):115 """Format a rupee amount into K / L / Cr for readability."""116 try:117 amount = float(amount)118 except (TypeError, ValueError):119 return "Rs. 0"120 neg = amount < 0121 a = abs(amount)122 if a >= 1_00_00_000: # 1 crore+123 s = f"Rs. {a / 1_00_00_000:.2f} Cr"124 elif a >= 1_00_000: # 1 lakh+125 s = f"Rs. {a / 1_00_000:.2f} L"126 elif a >= 1_000: # 1 thousand+127 s = f"Rs. {a / 1_000:.1f} K"128 else:129 s = f"Rs. {a:,.0f}"130 return f"-{s}" if neg else s131 132 133# ============================================================134# LOAD MODEL135# ============================================================136@st.cache_resource137def load_model():138 return pickle.load(open("finance_model.pkl", "rb"))139 140model = load_model()141 142 143# ============================================================144# HISTORY HELPERS145# ============================================================146HISTORY_FILE = "analysis_history.json"147 148def load_history():149 if os.path.exists(HISTORY_FILE):150 with open(HISTORY_FILE, "r") as f:151 return json.load(f)152 return []153 154def save_history(data):155 with open(HISTORY_FILE, "w") as f:156 json.dump(data, f, indent=2)157 158 159# ============================================================160# APP HEADER161# ============================================================162st.markdown("""163<div style='text-align:center; padding:30px 0 10px 0;'>164 <h1 style='font-size:42px; color:#38bdf8;'> AI Personal Wealth Analyzer</h1>165 <p style='color:#94a3b8; font-size:17px;'>166 </p>167</div>168""", unsafe_allow_html=True)169st.divider()170 171 172# ============================================================173# SECTION — INCOME INFORMATION174# ============================================================175section("", "Income Information")176 177c1, c2, c3 = st.columns(3)178with c1:179 income = st.number_input("Monthly Salary (Rs.)", min_value=0, value=0, step=1000)180with c2:181 extra_income = st.number_input("Extra Income (Rs.)", min_value=0, value=0, step=500)182with c3:183 savings = st.number_input("Current Savings (Rs.)", min_value=0, value=0, step=1000)184 185total_income = income + extra_income186 187if total_income > 0:188 st.info(f"Total Monthly Income: {fmt(total_income)}")189 190st.divider()191 192 193# ============================================================194# SECTION — LOAN INFORMATION195# ============================================================196section("", "Loan Information")197 198if "loans" not in st.session_state:199 st.session_state.loans = []200 201if st.button("Add Loan"):202 st.session_state.loans.append({"name": "", "emi": 0, "principal": 0, "months": 0})203 204loan_data = []205 206for i in range(len(st.session_state.loans)):207 st.markdown(f"**Loan {i + 1}**")208 c1, c2, c3, c4 = st.columns(4)209 name = c1.text_input("Loan Name", key=f"name{i}", placeholder="e.g. Home Loan")210 emi = c2.number_input("Monthly EMI (Rs.)", min_value=0, key=f"emi{i}")211 principal = c3.number_input("Principal Left (Rs.)", min_value=0, key=f"principal{i}")212 months = c4.number_input("Months Remaining", min_value=0, key=f"months{i}")213 loan_data.append({"name": name, "emi": emi, "principal": principal, "months": months})214 215df_loans = pd.DataFrame(loan_data) if loan_data else pd.DataFrame(216 columns=["name", "emi", "principal", "months"]217)218 219st.divider()220 221 222# ============================================================223# ANALYZE BUTTON224# ============================================================225_, mid_col, _ = st.columns([1, 2, 1])226with mid_col:227 if st.button("Analyze My Financial Health", use_container_width=True):228 st.session_state.analyzed = True229 230if "analyzed" not in st.session_state:231 st.session_state.analyzed = False232 233 234# ============================================================235# FULL ANALYSIS236# ============================================================237if st.session_state.analyzed:238 239 # Core values240 total_emi = int(df_loans["emi"].sum()) if not df_loans.empty else 0241 principal_left = int(df_loans["principal"].sum()) if not df_loans.empty else 0242 months_left = int(df_loans["months"].max()) if not df_loans.empty else 0243 loan_count = len(df_loans)244 balance = total_income - total_emi245 monthly_balance = balance246 247 st.divider()248 249 # ----------------------------------------------------------250 # FINANCIAL OVERVIEW — KPI Dashboard251 # ----------------------------------------------------------252 section("", "Financial Overview")253 254 c1, c2, c3, c4 = st.columns(4)255 c1.markdown(f'<div class="kpi">Income<br>{fmt(total_income)}</div>', unsafe_allow_html=True)256 c2.markdown(f'<div class="kpi">Total EMI<br>{fmt(total_emi)}</div>', unsafe_allow_html=True)257 c3.markdown(f'<div class="kpi">Balance<br>{fmt(balance)}</div>', unsafe_allow_html=True)258 c4.markdown(f'<div class="kpi">Loans<br>{loan_count}</div>', unsafe_allow_html=True)259 260 st.divider()261 262 # ----------------------------------------------------------263 # AI FINANCIAL STRESS PREDICTION264 # ----------------------------------------------------------265 section("", "AI Financial Stress Prediction")266 267 input_data = np.array([[income, extra_income, loan_count, total_emi, principal_left, months_left, savings]])268 pred = model.predict(input_data)269 270 bar = st.progress(0, text="Analyzing with AI model...")271 for i in range(100):272 time.sleep(0.008)273 bar.progress(i + 1, text=f"Analyzing... {i+1}%")274 bar.empty()275 276 if pred[0] == 0:277 st.success("Low Financial Stress")278 elif pred[0] == 1:279 st.warning("Moderate Financial Stress")280 else:281 st.error("High Financial Stress")282 283 st.divider()284 285 # ----------------------------------------------------------286 # DEBT RISK SCORE287 # ----------------------------------------------------------288 section("", "Debt Risk Score")289 290 risk_score = min(int((total_emi / max(total_income, 1)) * 100), 100)291 292 fig_gauge = go.Figure(go.Indicator(293 mode = "gauge+number+delta",294 value = risk_score,295 title = {"text": "EMI-to-Income Risk %", "font": {"size": 18}},296 delta = {"reference": 40,297 "increasing": {"color": "red"},298 "decreasing": {"color": "green"}},299 gauge = {300 "axis": {"range": [0, 100], "tickwidth": 1},301 "bar": {"color": "crimson"},302 "steps": [303 {"range": [0, 35], "color": "#22c55e"},304 {"range": [35, 60], "color": "#eab308"},305 {"range": [60,100], "color": "#ef4444"},306 ],307 "threshold": {"line": {"color": "white", "width": 3}, "value": 40}308 }309 ))310 fig_gauge.update_layout(height=300, margin=dict(t=50, b=0))311 st.plotly_chart(fig_gauge, use_container_width=True)312 313 if risk_score <= 35:314 st.success(f"Healthy — EMI is {risk_score}% of income (Safe zone is 35% or below)")315 elif risk_score <= 60:316 st.warning(f"Moderate — EMI is {risk_score}% of income (Caution zone 35–60%)")317 else:318 st.error(f"Danger — EMI is {risk_score}% of income (Critical above 60%)")319 320 st.divider()321 322 # ----------------------------------------------------------323 # FUTURE BALANCE PROJECTION324 # ----------------------------------------------------------325 section("", "Future Balance Prediction")326 327 years = st.slider("Prediction Years", 1, 10, 3)328 months_total = years * 12329 cumulative = float(savings)330 projection = []331 332 for m in range(1, months_total + 1):333 cumulative += monthly_balance334 projection.append({"Month": m, "Prediction Balance (Rs.)": cumulative})335 336 df_proj = pd.DataFrame(projection)337 fig_proj = px.area(338 df_proj, x="Month", y="Prediction Balance (Rs.)",339 color_discrete_sequence=["#38bdf8"],340 title=f"Balance Prediction over {years} Year(s)"341 )342 fig_proj.update_layout(343 plot_bgcolor="#0f172a", paper_bgcolor="#0f172a",344 font_color="white", height=350345 )346 st.plotly_chart(fig_proj, use_container_width=True)347 348 total_gain = monthly_balance * months_total349 if monthly_balance >= 0:350 st.success(351 f"SAFE ZONE — Saving {fmt(monthly_balance)}/month — "352 f"{fmt(total_gain)} total in {years} year(s)"353 )354 else:355 st.error(356 f"DANGER ZONE — Deficit {fmt(abs(monthly_balance))}/month — "357 f"Debt grows {fmt(abs(total_gain))} in {years} year(s)"358 )359 360 st.divider()361 362 # ----------------------------------------------------------363 # SECTION 1 — ADDITIONAL PAYMENT SIMULATOR (UPDATED)364 # ----------------------------------------------------------365 section("", "Additional Payment Simulator")366 367 sim_years = st.slider("Simulation Period (Years)", 1, 10, 3, key="sim_years")368 sim_months = sim_years * 12369 extra_payment = st.slider("Extra Monthly Payment Toward EMI (Rs.)", 0, 200000, 0, step=500)370 371 if total_emi > 0 and principal_left > 0:372 373 # Amortization — WITHOUT extra payment374 normal_months = 0375 bal = float(principal_left)376 while bal > 0 and normal_months < 99999:377 bal -= total_emi378 normal_months += 1379 380 # Amortization — WITH extra payment381 new_monthly = total_emi + extra_payment382 extra_months = 0383 bal2 = float(principal_left)384 while bal2 > 0 and extra_months < 99999:385 bal2 -= new_monthly386 extra_months += 1387 388 months_saved = max(0, normal_months - extra_months)389 390 # ── Row 1: Loan Close Time | Time Saved ──391 c1, c2 = st.columns(2)392 c1.metric(393 "Loan Close Time (With Extra Pay)",394 f"{extra_months} months",395 delta=f"-{months_saved} months saved",396 delta_color="inverse"397 )398 c2.metric("Time Saved", f"{months_saved} months")399 400 # ── Row 2: Time Saved Amount — EMI freed + income kept ──401 time_saved_emi_amount = months_saved * total_emi402 time_saved_income_amount = months_saved * total_income403 404 ts1, ts2 = st.columns(2)405 ts1.metric(406 "Time Saved — EMI Amount Freed",407 fmt(time_saved_emi_amount),408 help="Total EMI payments you avoid due to early closure"409 )410 ts2.metric(411 "Time Saved — Income You Keep",412 fmt(time_saved_income_amount),413 help="Total income in hand during months saved by closing early"414 )415 416 # ── Simulation: month-by-month balance — Normal vs Extra Pay ──417 sim_rows = []418 bal_normal = float(savings)419 bal_extra_path = float(savings)420 loan_bal_normal = float(principal_left)421 loan_bal_extra = float(principal_left)422 423 for m in range(1, sim_months + 1):424 # Normal path425 if loan_bal_normal > 0:426 loan_bal_normal = max(loan_bal_normal - total_emi, 0)427 bal_normal += (total_income - total_emi)428 else:429 bal_normal += total_income # loan done, full income kept430 431 # Extra payment path432 if loan_bal_extra > 0:433 pay_this_month = min(new_monthly, loan_bal_extra)434 loan_bal_extra = max(loan_bal_extra - new_monthly, 0)435 bal_extra_path += (total_income - pay_this_month)436 else:437 bal_extra_path += total_income # loan done, full income kept438 439 sim_rows.append({440 "Month": m,441 "Balance Without Extra": round(bal_normal, 0),442 "Balance With Extra Pay": round(bal_extra_path, 0),443 })444 445 df_sim = pd.DataFrame(sim_rows)446 447 final_normal = df_sim["Balance Without Extra"].iloc[-1]448 final_extra = df_sim["Balance With Extra Pay"].iloc[-1]449 balance_diff = final_extra - final_normal450 451 # ── DEBT AVOIDED — actual money saved = balance gained by extra pay ──452 # Debt avoided = what you would have paid in interest/EMI but won't453 debt_avoided = max(454 0,455 (total_emi * normal_months) - (new_monthly * extra_months) - principal_left456 )457 # Real balance gain = what you actually accumulate extra in your hand458 real_balance_gain = balance_diff # from simulation459 460 st.markdown(f"**Balance Accumulation Over {sim_years} Year(s) — Normal vs Extra Payment**")461 462 fig_sim = px.line(463 df_sim, x="Month",464 y=["Balance Without Extra", "Balance With Extra Pay"],465 title=f"Balance Comparison Over {sim_years} Year(s)",466 color_discrete_map={467 "Balance Without Extra": "#94a3b8",468 "Balance With Extra Pay": "#38bdf8",469 }470 )471 fig_sim.update_layout(472 plot_bgcolor="#0f172a", paper_bgcolor="#0f172a",473 font_color="white", height=340474 )475 st.plotly_chart(fig_sim, use_container_width=True)476 477 # ── KPI row: Balance Normal | Balance Extra | Debt Avoided (balance gain) ──478 ba1, ba2, ba3 = st.columns(3)479 ba1.metric(f"Balance After {sim_years}Y (Normal)", fmt(final_normal))480 ba2.metric(f"Balance After {sim_years}Y (Extra Pay)", fmt(final_extra))481 ba3.metric(482 "Debt Avoided (Extra Balance Gained)",483 fmt(real_balance_gain),484 delta=fmt(real_balance_gain),485 delta_color="normal",486 help=(487 f"By paying extra, you close {months_saved} months early. "488 f"Those freed months mean your balance is {fmt(real_balance_gain)} "489 f"higher than without extra payment. Interest/excess saved: {fmt(debt_avoided)}."490 )491 )492 493 if extra_payment > 0:494 sign = "📈 GAIN" if real_balance_gain >= 0 else "📉 LOSS"495 st.info(496 f"{sign} — Extra {fmt(extra_payment)}/month closes loan {months_saved} months early | "497 f"EMI freed: {fmt(time_saved_emi_amount)} | "498 f"Balance gained vs normal path: {fmt(real_balance_gain)} over {sim_years} yr(s)"499 )500 else:501 st.info("Add at least one loan with EMI and Principal to simulate additional payments.")502 503 st.divider()504 505 # ----------------------------------------------------------506 # SECTION 2 — LOAN CLOSE ADVICE507 # ----------------------------------------------------------508 section("", "Loan Close Advice")509 510 if not df_loans.empty and total_income > 0:511 daily_spend = total_income / 30512 family_expenses = total_income * 0.40513 personal_lifestyle = total_income * 0.15514 required_total = total_emi + family_expenses + personal_lifestyle515 extra_needed = max(0, required_total - total_income)516 517 c1, c2, c3 = st.columns(3)518 c1.metric("Current Total EMI", fmt(total_emi))519 c2.metric("Daily Spend (monthly basis)", f"{fmt(daily_spend)} per day")520 c3.metric("Extra Needed to Manage", f"{fmt(extra_needed)} per month")521 522 st.markdown("**Monthly Budget Required to Manage Smoothly:**")523 b1, b2, b3, b4 = st.columns(4)524 b1.metric("EMI Payments", fmt(total_emi))525 b2.metric("Family (40%)", fmt(family_expenses))526 b3.metric("Lifestyle (15%)", fmt(personal_lifestyle))527 b4.metric("Total Required", fmt(required_total))528 529 if extra_needed > 0:530 st.error(f"You need {fmt(extra_needed)} more per month to cover all obligations smoothly.")531 else:532 st.success("Your income comfortably covers EMI, family needs, and lifestyle.")533 534 top_loans = df_loans[df_loans["emi"] > 0].sort_values("emi", ascending=False)535 if not top_loans.empty:536 t = top_loans.iloc[0]537 st.warning(f"Close '{t['name']}' first — EMI relief of {fmt(t['emi'])} per month")538 else:539 st.info("Add loan and income data to see advice.")540 541 st.divider()542 543 # ----------------------------------------------------------544 # SECTION 3 — NET WORTH ANALYSIS (UPDATED)545 # ----------------------------------------------------------546 section("", "Net Worth Analysis")547 548 annual_income = total_income * 12549 emi_paid_1year = total_emi * 12550 principal_after_1yr = max(principal_left - emi_paid_1year, 0)551 balance_after_1yr = savings + (monthly_balance * 12)552 net_gain_loss = balance_after_1yr - savings553 current_networth = savings - principal_left554 future_savings = savings + monthly_balance * months_total555 future_networth = future_savings - principal_left556 557 st.markdown("**1-Year Financial Summary:**")558 c1, c2, c3, c4 = st.columns(4)559 c1.metric("Total 1-Year Income", fmt(annual_income))560 c2.metric("EMI Paid in 1 Year", fmt(emi_paid_1year))561 c3.metric("Balance After 1 Year", fmt(balance_after_1yr))562 c4.metric("Net Gain / Loss", fmt(net_gain_loss),563 delta=fmt(net_gain_loss), delta_color="normal")564 565 st.markdown("**Long-Term Net Worth (Principal Debt Included):**")566 n1, n2, n3 = st.columns(3)567 n1.metric(568 "Current Net Worth",569 fmt(current_networth),570 help=f"Savings {fmt(savings)} minus Principal Debt {fmt(principal_left)}"571 )572 n2.metric(573 "Net Worth After 1 Year",574 fmt(savings - principal_after_1yr),575 help=f"Principal remaining after 1 year: {fmt(principal_after_1yr)}"576 )577 n3.metric(578 f"Net Worth After {years} Years",579 fmt(future_networth),580 help=f"Projected future savings minus current principal"581 )582 583 st.markdown("**Principal Debt Breakdown:**")584 pd1, pd2, pd3 = st.columns(3)585 pd1.metric("Current Principal Debt", fmt(principal_left))586 pd2.metric("Principal After 1 Year", fmt(principal_after_1yr))587 pd3.metric(588 "Principal Reduced in 1 Year",589 fmt(principal_left - principal_after_1yr),590 delta=f"-{fmt(principal_left - principal_after_1yr)}",591 delta_color="inverse"592 )593 594 nw_df = pd.DataFrame({595 "Period": ["Today", "1 Year", f"{years} Years"],596 "Net Worth": [current_networth, savings - principal_after_1yr, future_networth]597 })598 fig_nw = px.bar(599 nw_df, x="Period", y="Net Worth",600 color="Net Worth",601 color_continuous_scale="Blues",602 title="Net Worth Over Time"603 )604 fig_nw.update_layout(605 plot_bgcolor="#0f172a", paper_bgcolor="#0f172a",606 font_color="white", height=300607 )608 st.plotly_chart(fig_nw, use_container_width=True)609 610 st.divider()611 612 # ----------------------------------------------------------613 # SECTION 4 — LOAN CLOSING STRATEGY614 # ----------------------------------------------------------615 section("", "Loan Closing Strategy")616 617 if not df_loans.empty and total_income > 0:618 df_strat = df_loans[df_loans["emi"] > 0].copy()619 df_strat = df_strat.sort_values("emi", ascending=False).reset_index(drop=True)620 emi_limit = total_income * 0.40621 running_emi = 0622 close_list = []623 safe_list = []624 625 for _, row in df_strat.iterrows():626 running_emi += row["emi"]627 if running_emi > emi_limit:628 close_list.append(row)629 else:630 safe_list.append(row)631 632 s1, s2 = st.columns(2)633 s1.metric("Safe EMI Limit (40% of Income)", fmt(emi_limit))634 over_under = "over" if total_emi > emi_limit else "under"635 s2.metric(636 "Your Current Total EMI",637 fmt(total_emi),638 delta=f"{fmt(abs(total_emi - emi_limit))} {over_under} limit",639 delta_color="inverse"640 )641 642 if close_list:643 st.error(644 f"{len(close_list)} loan(s) are pushing you beyond the 40% EMI safety limit. "645 f"Close these first:"646 )647 for row in close_list:648 mo_close = round(row["principal"] / row["emi"]) if row["emi"] > 0 else 0649 stress_pct = round((row["emi"] / max(total_emi, 1)) * 100, 1)650 extra_per_mo = max(0, row["emi"] - (emi_limit / max(len(df_strat), 1)))651 st.markdown(f"""652<div class="loan-card">653 <b>{row['name']}</b><br>654 EMI: {fmt(row['emi'])} | 655 Principal Left: {fmt(row['principal'])} | 656 Close in: ~{mo_close} months | 657 Extra needed: {fmt(extra_per_mo)}/month | 658 Stress reduction if closed: <b>{stress_pct}%</b>659</div>""", unsafe_allow_html=True)660 else:661 st.success("All loans are within the 40% EMI safety limit.")662 663 if safe_list:664 st.info(f"{len(safe_list)} loan(s) are within the safe EMI range:")665 for row in safe_list:666 st.markdown(f"""667<div class="loan-card">668 <b>{row['name']}</b> — EMI: {fmt(row['emi'])} (Safe — continue paying)669</div>""", unsafe_allow_html=True)670 671 monthly_extra_req = max(0, total_emi - emi_limit)672 if monthly_extra_req > 0:673 st.warning(674 f"You need {fmt(monthly_extra_req)} extra income per month "675 f"OR close high-EMI loans to reach the safe 40% zone."676 )677 else:678 st.info("Add loan and income data for strategy analysis.")679 680 st.divider()681 682 # ----------------------------------------------------------683 # SECTION — LOAN CLOSURE PREDICTION (REDESIGNED)684 # ----------------------------------------------------------685 section("", "Loan Closure Prediction")686 687 st.markdown(688 "This section uses the loans you already entered above. "689 "Select your prediction period and instantly see closure timelines, "690 "principal progress, balance impact, and smart suggestions."691 )692 693 # Use loans from the main Loan Information section (df_loans)694 valid_pred_loans = [695 {696 "name": row["name"] or f"Loan {i+1}",697 "emi": row["emi"],698 "principal": row["principal"],699 "months_rem": row["months"],700 }701 for i, row in df_loans.iterrows()702 if row["emi"] > 0 and row["principal"] > 0703 ]704 705 pred_years_sel = st.selectbox("Prediction Period", [1, 2, 3, 5], index=0, key="pred_years_sel")706 pred_months_total = pred_years_sel * 12707 708 if valid_pred_loans:709 710 # ── Per-loan closure summary ──711 st.markdown(f"**📋 Loan Closure Summary — {pred_years_sel} Year View:**")712 713 pred_summary_rows = []714 for loan in valid_pred_loans:715 m_to_close = int(loan["months_rem"]) if loan["months_rem"] > 0 \716 else int(loan["principal"] / loan["emi"])717 closes_within = m_to_close <= pred_months_total718 if closes_within:719 status = f"✅ Closes at Month {m_to_close}"720 else:721 beyond = m_to_close - pred_months_total722 status = f"⚠️ {beyond} months beyond {pred_years_sel}yr"723 724 # Principal remaining after pred_months_total725 months_paid_so_far = min(m_to_close, pred_months_total)726 principal_remaining = max(loan["principal"] - loan["emi"] * months_paid_so_far, 0)727 principal_cleared = loan["principal"] - principal_remaining728 pct_cleared = round((principal_cleared / max(loan["principal"], 1)) * 100, 1)729 730 pred_summary_rows.append({731 "Loan": loan["name"],732 "Monthly EMI": fmt(loan["emi"]),733 "Starting Principal": fmt(loan["principal"]),734 "Months to Close": m_to_close,735 "Principal Cleared": fmt(principal_cleared),736 "Principal Remaining": fmt(principal_remaining),737 "% Cleared": f"{pct_cleared}%",738 "Status": status,739 })740 741 df_pred_table = pd.DataFrame(pred_summary_rows)742 st.dataframe(df_pred_table, use_container_width=True)743 744 # ── Principal Progress bar per loan ──745 st.markdown("** Principal Clearance Progress per Loan:**")746 for row in pred_summary_rows:747 pct_val = float(row["% Cleared"].replace("%", ""))748 colour = "normal" if pct_val >= 100 else ("normal" if pct_val > 50 else "off")749 st.markdown(f"**{row['Loan']}** — Cleared: {row['Principal Cleared']} | Remaining: {row['Principal Remaining']} ({row['% Cleared']})")750 st.progress(min(int(pct_val), 100))751 752 # ── Month-by-month 12-month rolling prediction ──753 st.markdown("** Month-by-Month 1-Year Prediction (Balance with Loan Closures):**")754 755 running_loans_pred = []756 for loan in valid_pred_loans:757 m_rem = int(loan["months_rem"]) if loan["months_rem"] > 0 \758 else int(loan["principal"] / loan["emi"])759 running_loans_pred.append({760 "name": loan["name"],761 "emi": loan["emi"],762 "bal": float(loan["principal"]),763 "months_rem": m_rem,764 "closed": False,765 "closed_at": None,766 })767 768 cum_balance_pred = float(savings)769 monthly_pred_rows = []770 771 for m in range(1, 13):772 active_emi_pred = sum(l["emi"] for l in running_loans_pred if not l["closed"])773 net_this_month = total_income - active_emi_pred774 cum_balance_pred += net_this_month775 776 closed_names_this = []777 for l in running_loans_pred:778 if not l["closed"]:779 l["bal"] -= l["emi"]780 l["months_rem"] -= 1781 if l["bal"] <= 0 or l["months_rem"] <= 0:782 l["closed"] = True783 l["closed_at"] = m784 closed_names_this.append(l["name"])785 786 monthly_pred_rows.append({787 "Month": m,788 "Active EMI": fmt(active_emi_pred),789 "Net This Month": fmt(net_this_month),790 "Cumulative Balance": round(cum_balance_pred, 0),791 "Loan(s) Closed": ", ".join(closed_names_this) if closed_names_this else "—",792 })793 794 df_monthly_pred = pd.DataFrame(monthly_pred_rows)795 796 # display with formatted balance797 df_display = df_monthly_pred.copy()798 df_display["Cumulative Balance"] = df_display["Cumulative Balance"].apply(fmt)799 st.dataframe(df_display, use_container_width=True)800 801 # ── Balance area chart ──802 df_chart_pred = pd.DataFrame({803 "Month": [r["Month"] for r in monthly_pred_rows],804 "Balance": [r["Cumulative Balance"] for r in monthly_pred_rows],805 })806 fig_pred_bal = px.area(807 df_chart_pred, x="Month", y="Balance",808 color_discrete_sequence=["#38bdf8"],809 title="Predicted Balance Over 12 Months (Loan Closures Factored In)"810 )811 fig_pred_bal.update_layout(812 plot_bgcolor="#0f172a", paper_bgcolor="#0f172a",813 font_color="white", height=320814 )815 st.plotly_chart(fig_pred_bal, use_container_width=True)816 817 # ── 1-Year Summary KPIs ──818 final_balance_pred = df_chart_pred["Balance"].iloc[-1]819 loans_closed_pred = [l for l in running_loans_pred if l["closed"]]820 loans_open_pred = [l for l in running_loans_pred if not l["closed"]]821 freed_emi_pred = sum(l["emi"] for l in loans_closed_pred)822 823 sk1, sk2, sk3, sk4 = st.columns(4)824 sk1.metric("Balance After 1 Year", fmt(final_balance_pred))825 sk2.metric("Loans Closed Within 1 Year", str(len(loans_closed_pred)))826 sk3.metric("Loans Still Active After 1 Year", str(len(loans_open_pred)))827 sk4.metric("Monthly EMI Freed After Closures", fmt(freed_emi_pred))828 829 # ── Multi-year balance impact with loan-closure waterfall ──830 st.markdown(f"**📈 {pred_years_sel}-Year Balance Projection (Dynamic — Closures Applied):**")831 832 # Re-run full multi-year simulation833 ml2 = []834 for loan in valid_pred_loans:835 m_rem = int(loan["months_rem"]) if loan["months_rem"] > 0 \836 else int(loan["principal"] / loan["emi"])837 ml2.append({838 "name": loan["name"], "emi": loan["emi"],839 "bal": float(loan["principal"]),840 "months_rem": m_rem, "closed": False841 })842 843 cum2 = float(savings)844 proj_rows = []845 for m in range(1, pred_months_total + 1):846 act_emi = sum(l["emi"] for l in ml2 if not l["closed"])847 cum2 += (total_income - act_emi)848 for l in ml2:849 if not l["closed"]:850 l["bal"] -= l["emi"]851 l["months_rem"] -= 1852 if l["bal"] <= 0 or l["months_rem"] <= 0:853 l["closed"] = True854 proj_rows.append({"Month": m, "Balance": round(cum2, 0)})855 856 df_proj2 = pd.DataFrame(proj_rows)857 start_bal = float(savings)858 end_bal = df_proj2["Balance"].iloc[-1]859 delta_bal = end_bal - start_bal860 861 fig_proj2 = px.area(862 df_proj2, x="Month", y="Balance",863 color_discrete_sequence=["#22c55e" if delta_bal >= 0 else "#ef4444"],864 title=f"{pred_years_sel}-Year Balance Projection with Actual Loan Closure Events"865 )866 fig_proj2.update_layout(867 plot_bgcolor="#0f172a", paper_bgcolor="#0f172a",868 font_color="white", height=340869 )870 st.plotly_chart(fig_proj2, use_container_width=True)871 872 # ── GAIN / LOSS ALERT ──873 gain_label = "📈 BALANCE GAIN" if delta_bal >= 0 else "📉 BALANCE LOSS"874 card_class = "alert-gain" if delta_bal >= 0 else "alert-loss"875 st.markdown(f"""876<div class="{card_class}">877 <b>{gain_label} over {pred_years_sel} Year(s)</b><br>878 Starting Balance: {fmt(start_bal)} → 879 Ending Balance: {fmt(end_bal)}<br>880 Change: <b>{fmt(delta_bal)}</b>881 {" ✅ Your loans closing early free up cash, boosting your balance!" if delta_bal >= 0882 else " ⚠️ EMI burden exceeds income. Close high-EMI loans to reverse this."}883</div>""", unsafe_allow_html=True)884 885 # ── HOW LONG TO CLOSE TOTAL PRINCIPAL in 1 year ──886 st.markdown("** Principal Closure Timeline & Suggestions:**")887 888 total_principal_all = sum(l["principal"] for l in valid_pred_loans)889 total_emi_all = sum(l["emi"] for l in valid_pred_loans)890 months_to_clear_all = int(total_principal_all / total_emi_all) if total_emi_all > 0 else 999891 892 pc1, pc2 = st.columns(2)893 pc1.metric("Total Principal (All Loans)", fmt(total_principal_all))894 pc2.metric(895 "Estimated Months to Clear All Principal",896 f"{months_to_clear_all} months ({months_to_clear_all/12:.1f} yrs)"897 )898 899 # Suggestion: how much extra to close all within 1 year900 if months_to_clear_all > 12:901 # Extra needed per month so principal clears in 12 months902 extra_to_close_1yr = max(0, int(total_principal_all / 12) - total_emi_all)903 st.markdown(f"""904<div class="info-card">905 💡 <b>Suggestion:</b> To close <b>all principal within 1 year</b>, you need to pay906 an extra <b>{fmt(extra_to_close_1yr)}/month</b> on top of your current EMI of {fmt(total_emi_all)}.907 <br>Total monthly payment required: <b>{fmt(total_emi_all + extra_to_close_1yr)}</b>908</div>""", unsafe_allow_html=True)909 else:910 st.success(f"✅ At current EMI rate, all principal clears within {months_to_clear_all} months — well within 1 year!")911 912 # Per-loan suggestion913 st.markdown("**Per-Loan Closure Suggestions:**")914 for loan in valid_pred_loans:915 m_rem = int(loan["months_rem"]) if loan["months_rem"] > 0 \916 else int(loan["principal"] / loan["emi"])917 if m_rem > 12:918 extra_needed_1yr = max(0, int(loan["principal"] / 12) - loan["emi"])919 st.markdown(f"""920<div class="info-card">921 <b>{loan['name']}</b>: Closes in {m_rem} months. 922 Pay extra <b>{fmt(extra_needed_1yr)}/month</b> to close within 1 year.923 (Current EMI: {fmt(loan['emi'])} → Required: {fmt(loan['emi'] + extra_needed_1yr)}/month)924</div>""", unsafe_allow_html=True)925 else:926 st.markdown(f"""927<div class="alert-gain">928 ✅ <b>{loan['name']}</b>: Closes in {m_rem} months — within 1 year at current EMI. No extra payment needed.929</div>""", unsafe_allow_html=True)930 931 # ── Loan closure events summary ──932 if loans_closed_pred:933 st.success(934 f"Loans closing within 1 year: "935 f"{', '.join(l['name'] for l in loans_closed_pred)} — "936 f"EMI freed: {fmt(freed_emi_pred)}/month after closure"937 )938 if loans_open_pred:939 st.info(940 f"Loans still active after 1 year: "941 f"{', '.join(l['name'] for l in loans_open_pred)}"942 )943 944 else:945 st.info(946 "Add loans in the **Loan Information** section above (EMI + Principal required) "947 "to see closure predictions here."948 )949 950 st.divider()951 952 # ----------------------------------------------------------953 # SECTION 5 — EXTRA INCOME SUGGESTIONS954 # ----------------------------------------------------------955 section("", "Extra Income Suggestions")956 957 family_expenses = total_income * 0.40958 personal_lifestyle = total_income * 0.15959 required_total = total_emi + family_expenses + personal_lifestyle960 extra_income_needed = max(0, required_total - total_income)961 962 st.markdown("**Monthly Obligation Breakdown:**")963 e1, e2, e3, e4 = st.columns(4)964 e1.metric("EMI Total", fmt(total_emi))965 e2.metric("Family (40%)", fmt(family_expenses))966 e3.metric("Lifestyle (15%)", fmt(personal_lifestyle))967 e4.metric("Total Required", fmt(required_total))968 969 if extra_income_needed > 0:970 st.error(f"You need {fmt(extra_income_needed)} extra per month to cover all obligations.")971 allocations = [972 ("Freelancing / Consulting", 0.40),973 ("Online Business (Flipkart / Amazon)", 0.20),974 ("Teaching / Online Courses", 0.15),975 ("Investments (Index Funds / Stocks)", 0.15),976 ("Content Creation / YouTube", 0.10),977 ]978 st.markdown("**Income Target Allocation Plan:**")979 for label, share in allocations:980 target = round(extra_income_needed * share)981 st.markdown(982 f'<div class="info-card">'983 f'<b>{label}</b> — Target: {fmt(target)}/month ({int(share*100)}% of extra needed)'984 f'</div>',985 unsafe_allow_html=True986 )987 else:988 surplus = total_income - required_total989 st.success(f"Income covers all obligations. Monthly surplus: {fmt(surplus)}")990 st.write(f"Total obligations: {fmt(required_total)} | Your income: {fmt(total_income)}")991 992 st.divider()993 994 # ----------------------------------------------------------995 # STRESS SOURCE DETECTION996 # ----------------------------------------------------------997 section("", "Stress Source Detection")998 999 if not df_loans.empty:1000 stress_df = df_loans[df_loans["emi"] > 0].sort_values("emi", ascending=False)1001 if not stress_df.empty:1002 top = stress_df.iloc[0]1003 pct = round((top["emi"] / max(total_emi, 1)) * 100, 1)1004 st.error(1005 f"Highest stress loan: {top['name']} — "1006 f"EMI {fmt(top['emi'])} ({pct}% of total EMI)"1007 )1008 fig_pie = px.pie(1009 df_loans[df_loans["emi"] > 0],1010 values="emi", names="name",1011 title="EMI Distribution Across Loans",1012 color_discrete_sequence=px.colors.sequential.Blues_r1013 )1014 fig_pie.update_layout(1015 paper_bgcolor="#0f172a", font_color="white", height=3201016 )1017 st.plotly_chart(fig_pie, use_container_width=True)1018 else:1019 st.info("No loan data to detect stress source.")1020 1021 st.divider()1022 1023 # ----------------------------------------------------------1024 # AI FINANCIAL ADVISOR1025 # ----------------------------------------------------------1026 section("", "AI Financial Advisor")1027 1028 q = st.text_input(1029 "Ask a finance question:",1030 placeholder="e.g. How can I reduce my loan burden?"1031 )1032 if q:1033 ql = q.lower()1034 if any(w in ql for w in ["loan", "emi", "debt", "close", "pay"]):1035 st.write(1036 "Focus on clearing the highest-EMI loan first to reduce financial stress fastest. "1037 "Even an extra Rs. 1,000–2,000 per month toward the principal accelerates payoff."1038 )1039 elif any(w in ql for w in ["save", "saving", "savings", "emergency"]):1040 st.write(1041 "Build an emergency fund of 6 months of expenses before aggressive investing. "1042 "Keep it in a liquid fund or high-interest savings account."1043 )1044 elif any(w in ql for w in ["invest", "investment", "stock", "mutual", "fund"]):1045 st.write(1046 "Start with index funds (Nifty 50 / Sensex) for stable long-term growth. "1047 "Invest at least 10–15% of income monthly via SIP once EMI is under control."1048 )1049 elif any(w in ql for w in ["income", "earn", "salary", "extra"]):1050 st.write(1051 "Focus on one high-return side income stream first. Freelancing or consulting "1052 "in your primary skill is the fastest way to add Rs. 5,000–20,000 per month."1053 )1054 elif any(w in ql for w in ["budget", "plan", "manage", "spend"]):1055 st.write(1056 "Follow the 50/30/20 rule: 50% for needs (EMI + family), "1057 "30% for wants, 20% for savings and investments. "1058 "Automate EMI payments to avoid penalties."1059 )1060 else:1061 st.write(1062 "General rule: Keep total EMI below 40% of income. "1063 "Maintain 6 months emergency fund. "1064 "Invest 15% or more in diversified instruments once debt is managed."1065 )1066 1067 st.divider()1068 1069 # ----------------------------------------------------------1070 # SECTION 6 — ANALYSIS HISTORY & DELETE1071 # ----------------------------------------------------------1072 section("️", "Analysis History")1073 1074 history = load_history()1075 1076 if st.button("Save Current Analysis to History"):1077 entry = {1078 "id": str(int(time.time())),1079 "date": datetime.now().strftime("%d %b %Y, %H:%M"),1080 "income": total_income,1081 "total_emi": total_emi,1082 "balance": balance,1083 "principal_left": principal_left,1084 "loan_count": loan_count,1085 "risk_score": risk_score,1086 "loans": loan_data1087 }1088 history.append(entry)1089 save_history(history)1090 st.success("Analysis saved successfully!")1091 st.rerun()1092 1093 if history:1094 st.markdown(f"**{len(history)} saved record(s):**")1095 for entry in reversed(history):1096 label = (1097 f"{entry['date']} | "1098 f"Income: {fmt(entry['income'])} | "1099 f"EMI: {fmt(entry['total_emi'])} | "1100 f"Risk: {entry['risk_score']}%"1101 )1102 with st.expander(label):1103 h1, h2, h3, h4 = st.columns(4)1104 h1.metric("Income", fmt(entry['income']))1105 h2.metric("Total EMI", fmt(entry['total_emi']))1106 h3.metric("Balance", fmt(entry['balance']))1107 h4.metric("Risk Score", f"{entry['risk_score']}%")1108 st.write(1109 f"Loans: {entry['loan_count']} | "1110 f"Principal Left: {fmt(entry['principal_left'])}"1111 )1112 if st.button("Delete this record", key=f"del_{entry['id']}"):1113 history = [h for h in history if h["id"] != entry["id"]]1114 save_history(history)1115 st.warning("Record deleted.")1116 st.rerun()1117 else:1118 st.info("No saved analyses yet. Click 'Save Current Analysis' above after analyzing.")1119 1120 st.divider()1121 1122 # ----------------------------------------------------------1123 # SECTION 7 — QUICK CALCULATOR1124 # ----------------------------------------------------------1125 section("", "Quick Calculator")1126 1127 tab_emi, tab_savings, tab_invest = st.tabs([1128 "EMI Calculator",1129 "Savings Goal",1130 "Investment Return (SIP)"1131 ])1132 1133 with tab_emi:1134 st.markdown("**Calculate EMI for any loan instantly**")1135 t1c1, t1c2, t1c3 = st.columns(3)1136 q_principal = t1c1.number_input("Loan Amount (Rs.)", min_value=0, value=500000, step=10000, key="qc_p")1137 q_rate = t1c2.number_input("Annual Interest Rate (%)", min_value=0.0, value=10.0, step=0.1, key="qc_r")1138 q_tenure = t1c3.number_input("Tenure (Months)", min_value=1, value=60, key="qc_t")1139 if q_principal > 0 and q_rate > 0 and q_tenure > 0:1140 r = q_rate / (12 * 100)1141 emi_calc = q_principal * r * (1 + r)**q_tenure / ((1 + r)**q_tenure - 1)1142 total_pay = emi_calc * q_tenure1143 total_int = total_pay - q_principal1144 rc1, rc2, rc3 = st.columns(3)1145 rc1.metric("Monthly EMI", fmt(emi_calc))1146 rc2.metric("Total Interest", fmt(total_int))1147 rc3.metric("Total Payment", fmt(total_pay))1148 fig_ep = px.pie(1149 values=[q_principal, total_int],1150 names=["Principal", "Interest"],1151 color_discrete_sequence=["#38bdf8", "#ef4444"],1152 title="Principal vs Interest Split"1153 )1154 fig_ep.update_layout(paper_bgcolor="#0f172a", font_color="white", height=280)1155 st.plotly_chart(fig_ep, use_container_width=True)1156 1157 with tab_savings:1158 st.markdown("**How long to reach your savings target?**")1159 sg1, sg2 = st.columns(2)1160 goal_amount = sg1.number_input("Target Savings (Rs.)", min_value=0, value=1000000, step=50000, key="sg_g")1161 monthly_save = sg2.number_input("Monthly Saving (Rs.)", min_value=0, value=10000, step=1000, key="sg_m")1162 if monthly_save > 0 and goal_amount > 0:1163 months_to_goal = goal_amount / monthly_save1164 st.metric(1165 "Time to Reach Goal",1166 f"{months_to_goal:.0f} months ({months_to_goal/12:.1f} years)"1167 )1168 progress_pct = min(int((savings / goal_amount) * 100), 100)1169 st.write(f"Current savings: {fmt(savings)} — {progress_pct}% of goal reached")1170 st.progress(progress_pct)1171 1172 with tab_invest:1173 st.markdown("**SIP compound return calculator**")1174 ir1, ir2, ir3 = st.columns(3)1175 inv_amount = ir1.number_input("Monthly Investment (Rs.)", min_value=0, value=5000, step=500, key="ir_a")1176 inv_rate = ir2.number_input("Expected Annual Return (%)", min_value=0.0, value=12.0, step=0.5, key="ir_r")1177 inv_years = ir3.number_input("Investment Period (Years)", min_value=1, value=10, key="ir_y")1178 if inv_amount > 0 and inv_rate > 0:1179 r_m = inv_rate / (12 * 100)1180 n_m = inv_years * 121181 fv = inv_amount * ((1 + r_m)**n_m - 1) / r_m * (1 + r_m)1182 invested = inv_amount * n_m1183 ret = fv - invested1184 i1, i2, i3 = st.columns(3)1185 i1.metric("Total Invested", fmt(invested))1186 i2.metric("Estimated Returns", fmt(ret))1187 i3.metric("Future Value", fmt(fv))1188 sip_rows = []1189 cum = 0.0; inv_cum = 0.01190 for m in range(1, n_m + 1):1191 cum = cum * (1 + r_m) + inv_amount1192 inv_cum += inv_amount1193 sip_rows.append({"Month": m, "Portfolio Value": cum, "Amount Invested": inv_cum})1194 df_sip = pd.DataFrame(sip_rows)1195 fig_sip = px.line(1196 df_sip, x="Month", y=["Portfolio Value", "Amount Invested"],1197 title=f"SIP Growth over {inv_years} Year(s)",1198 color_discrete_map={"Portfolio Value": "#38bdf8", "Amount Invested": "#94a3b8"}1199 )1200 fig_sip.update_layout(