Spaces:
Sleeping
Sleeping
| """ | |
| LendSure AI β Loan Approval Intelligence Platform | |
| app.py | Hugging Face Spaces (Streamlit SDK) | |
| Author : Karthika Krishna M | github.com/KARTHIKAKRISHNA123 | |
| Exact pipeline from Credit_Wise_Loan_Approval_System.ipynb: | |
| Cell 12 β Impute numerics (mean) | |
| Cell 14 β Impute categoricals (most_frequent) | |
| Cell 41 β Drop Applicant_ID | |
| Cell 46 β LabelEncode: Education_Level, Loan_Approved | |
| Cell 48 β OHE (drop=first): Employment_Status, Marital_Status, | |
| Loan_Purpose, Property_Area, Gender, Employer_Category | |
| Cell 80 β DTI_Ratio_sq = DTI_Ratio**2 | |
| Credit_Score_sq = Credit_Score**2 | |
| Drop: Credit_Score, DTI_Ratio | |
| (Applicant_Income_log is COMMENTED OUT in notebook) | |
| Cell 65 β StandardScaler | |
| Final feature set: 27 columns (verified by retraining) | |
| """ | |
| import streamlit as st | |
| import numpy as np | |
| import pandas as pd | |
| import pickle | |
| from pathlib import Path | |
| st.set_page_config( | |
| page_title="LendSure AI", | |
| page_icon="π¦", | |
| layout="wide", | |
| initial_sidebar_state="expanded", | |
| ) | |
| # ββ Navy-blue accent on HF default theme β no split divs βββββββββββββββββββββ | |
| st.markdown(""" | |
| <style> | |
| :root { | |
| --navy: #1B3A6B; | |
| --navym: #2563A8; | |
| --navyl: #EBF2FF; | |
| --grn: #166534; | |
| --grnbg: #F0FDF4; | |
| --red: #991B1B; | |
| --redbg: #FEF2F2; | |
| } | |
| .ls-header { | |
| background: linear-gradient(120deg,#1B3A6B 0%,#2563A8 60%,#3B82F6 100%); | |
| border-radius:10px; padding:28px 32px; margin-bottom:20px; | |
| } | |
| .ls-header h1 { | |
| color:#fff !important; font-size:1.9rem !important; | |
| font-weight:700 !important; margin:0 0 6px !important; | |
| } | |
| .ls-header p { color:rgba(255,255,255,.85) !important; font-size:.88rem; margin:0; } | |
| .ls-sec { | |
| font-size:.72rem; font-weight:700; letter-spacing:1px; | |
| text-transform:uppercase; color:var(--navym); | |
| border-bottom:2px solid var(--navyl); | |
| padding-bottom:6px; margin-bottom:14px; margin-top:4px; | |
| } | |
| .res-ok { | |
| background:var(--grnbg); border:2px solid #16A34A; | |
| border-radius:10px; padding:24px 16px; | |
| text-align:center; margin-bottom:16px; | |
| } | |
| .res-no { | |
| background:var(--redbg); border:2px solid #DC2626; | |
| border-radius:10px; padding:24px 16px; | |
| text-align:center; margin-bottom:16px; | |
| } | |
| .res-wait { | |
| border:2px dashed #CBD5E1; border-radius:10px; | |
| padding:36px 16px; text-align:center; | |
| margin-bottom:16px; color:#94A3B8; | |
| } | |
| .res-ico { font-size:2.5rem; line-height:1; margin-bottom:8px; } | |
| .res-title { font-size:1.25rem; font-weight:700; margin-bottom:4px; } | |
| .res-sub { font-size:.83rem; opacity:.8; } | |
| .pill { | |
| display:inline-block; background:var(--navyl); color:var(--navy); | |
| border-radius:20px; padding:3px 11px; | |
| font-size:.75rem; font-weight:600; margin:2px 3px 4px 0; | |
| } | |
| .pill-m { | |
| display:inline-block; background:var(--navy); color:#fff; | |
| border-radius:20px; padding:4px 14px; | |
| font-size:.76rem; font-weight:600; margin-bottom:10px; | |
| } | |
| .sc-row { | |
| display:flex; justify-content:space-between; | |
| font-size:.8rem; margin-bottom:2px; | |
| } | |
| .sc-lbl { font-weight:600; } | |
| .sc-val { color:#64748B; } | |
| .ins-row { display:flex; gap:12px; margin-bottom:14px; align-items:flex-start; } | |
| .ins-ico { font-size:1.3rem; flex-shrink:0; } | |
| .ins-ttl { font-weight:600; font-size:.87rem; } | |
| .ins-dsc { font-size:.79rem; color:#64748B; margin-top:2px; } | |
| .ls-foot { | |
| text-align:center; font-size:.74rem; color:#94A3B8; | |
| border-top:1px solid #E2E8F0; padding-top:16px; margin-top:24px; | |
| } | |
| .ls-foot a { color:var(--navym); text-decoration:none; } | |
| .stProgress>div>div { background:var(--navym) !important; } | |
| .stButton>button { | |
| background:var(--navy) !important; color:#fff !important; | |
| border:none !important; border-radius:8px !important; | |
| font-weight:600 !important; width:100%; transition:background .15s !important; | |
| } | |
| .stButton>button:hover { background:var(--navym) !important; } | |
| #MainMenu,footer,header { visibility:hidden; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CONSTANTS β exact OHE column order from notebook Cell 48 | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| OHE_COLS = ["Employment_Status","Marital_Status","Loan_Purpose", | |
| "Property_Area","Gender","Employer_Category"] | |
| # UI options β from CSV unique values (post-imputation) | |
| EMPLOYMENT_OPTS = ["Salaried","Self-employed","Contract","Unemployed"] | |
| MARITAL_OPTS = ["Married","Single"] | |
| PURPOSE_OPTS = ["Personal","Car","Business","Home","Education"] | |
| PROPERTY_OPTS = ["Urban","Semiurban","Rural"] | |
| GENDER_OPTS = ["Male","Female"] | |
| EMPLOYER_OPTS = ["Private","Government","MNC","Business","Unemployed"] | |
| EDUCATION_OPTS = ["Graduate","Not Graduate"] | |
| MODEL_OPTIONS = { | |
| "β Logistic Regression (Best)": "model_lr.pkl", | |
| "K-Nearest Neighbours (KNN)": "model_knn.pkl", | |
| "Naive Bayes (Best Precision)": "model_nb.pkl", | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # LOADERS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_scaler(): | |
| p = Path("scaler.pkl") | |
| return pickle.load(open(p,"rb")) if p.exists() else None | |
| def load_encoder(): | |
| p = Path("encoder.pkl") | |
| return pickle.load(open(p,"rb")) if p.exists() else None | |
| def load_model(name:str): | |
| p = Path(name) | |
| return pickle.load(open(p,"rb")) if p.exists() else None | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PREPROCESSING β verified against 27-column feature set | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def preprocess(raw:dict, scaler, encoder) -> np.ndarray: | |
| # LabelEncode Education_Level (Graduate=0, Not Graduate=1) | |
| edu_enc = 1 if raw["Education_Level"] == "Not Graduate" else 0 | |
| # Numerical features | |
| num = { | |
| "Applicant_Income": raw["Applicant_Income"], | |
| "Coapplicant_Income": raw["Coapplicant_Income"], | |
| "Age": raw["Age"], | |
| "Dependents": raw["Dependents"], | |
| "Existing_Loans": raw["Existing_Loans"], | |
| "Savings": raw["Savings"], | |
| "Collateral_Value": raw["Collateral_Value"], | |
| "Loan_Amount": raw["Loan_Amount"], | |
| "Loan_Term": raw["Loan_Term"], | |
| "Education_Level": edu_enc, | |
| # Engineered features (Cell 80) β Credit_Score & DTI_Ratio originals dropped | |
| "DTI_Ratio_sq": raw["DTI_Ratio"] ** 2, | |
| "Credit_Score_sq": raw["Credit_Score"] ** 2, | |
| } | |
| # OHE categorical block | |
| cat_df = pd.DataFrame([{c: raw[c] for c in OHE_COLS}]) | |
| if encoder is not None: | |
| ohe_arr = encoder.transform(cat_df) | |
| ohe_df = pd.DataFrame(ohe_arr, columns=encoder.get_feature_names_out(OHE_COLS)) | |
| else: | |
| ohe_df = pd.get_dummies(cat_df, drop_first=True) | |
| num_df = pd.DataFrame([num]) | |
| full_df = pd.concat([num_df.reset_index(drop=True), | |
| ohe_df.reset_index(drop=True)], axis=1) | |
| # ββ CRITICAL: reindex to EXACT column order scaler was fitted on ββββββββββ | |
| # Verified order from scaler.feature_names_in_ (27 columns): | |
| # [0-9] numerics + Education_Level | |
| # [10-24] OHE columns in encoder.get_feature_names_out() order | |
| # [25-26] DTI_Ratio_sq, Credit_Score_sq | |
| EXACT_COLS = ( | |
| ["Applicant_Income","Coapplicant_Income","Age","Dependents", | |
| "Existing_Loans","Savings","Collateral_Value","Loan_Amount", | |
| "Loan_Term","Education_Level"] | |
| + list(encoder.get_feature_names_out(OHE_COLS)) | |
| + ["DTI_Ratio_sq","Credit_Score_sq"] | |
| ) if encoder is not None else list(full_df.columns) | |
| full_df = full_df.reindex(columns=EXACT_COLS, fill_value=0) | |
| if scaler is not None: | |
| return scaler.transform(full_df) | |
| return full_df.values | |
| def credit_band(score:int): | |
| if score >= 750: return "Excellent","π’" | |
| if score >= 700: return "Good","π‘" | |
| if score >= 650: return "Fair","π " | |
| return "Poor","π΄" | |
| def fmt(v:float)->str: | |
| if v>=1e7: return f"βΉ{v/1e7:.1f}Cr" | |
| if v>=1e5: return f"βΉ{v/1e5:.1f}L" | |
| return f"βΉ{v:,.0f}" | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SIDEBAR | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with st.sidebar: | |
| st.markdown("### π¦ LendSure AI") | |
| st.caption("Loan Intelligence Platform") | |
| st.divider() | |
| page = st.radio("Navigate", | |
| ["π Predict","π Insights","π€ Models","βΉοΈ About"]) | |
| st.divider() | |
| st.markdown("**Select Model**") | |
| sel_label = st.radio("Model", list(MODEL_OPTIONS.keys()), | |
| label_visibility="collapsed") | |
| sel_pkl = MODEL_OPTIONS[sel_label] | |
| short = sel_label.split("(")[0].strip() | |
| st.divider() | |
| st.markdown(f""" | |
| **Active:** {short} | |
| **Dataset:** 1,000 records Β· 19 features | |
| **Features used:** 27 (post-engineering) | |
| **Models:** LR Β· KNN Β· Naive Bayes | |
| **Status:** π’ Running | |
| """) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # HEADER | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.markdown( | |
| "<div class='ls-header'>" | |
| "<h1>π¦ LendSure AI</h1>" | |
| "<p>ML-powered loan approval Β· Logistic Regression Β· KNN Β· Naive Bayes Β· Transparent Β· Fast</p>" | |
| "</div>", | |
| unsafe_allow_html=True, | |
| ) | |
| scaler = load_scaler() | |
| encoder = load_encoder() | |
| missing = [n for n,o in [("scaler.pkl",scaler),("encoder.pkl",encoder)] if o is None] | |
| if missing: | |
| st.warning(f"β οΈ {' and '.join(missing)} not found β demo mode. " | |
| "Upload all pkl files for real predictions.", icon="π") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PAGE: PREDICT | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if "Predict" in page: | |
| col_l, col_r = st.columns([1.05,1], gap="large") | |
| with col_l: | |
| # Section 1 β Applicant Profile | |
| st.markdown("<div class='ls-sec'>π€ Applicant Profile</div>", | |
| unsafe_allow_html=True) | |
| a1,a2 = st.columns(2) | |
| with a1: | |
| age = st.number_input("Age", 18, 75, 35) | |
| gender = st.selectbox("Gender", GENDER_OPTS) | |
| marital= st.selectbox("Marital Status", MARITAL_OPTS) | |
| with a2: | |
| dep = st.number_input("Dependents", 0, 10, 1) | |
| edu = st.selectbox("Education Level", EDUCATION_OPTS) | |
| emp_st = st.selectbox("Employment Status", EMPLOYMENT_OPTS) | |
| emp_cat = st.selectbox("Employer Category", EMPLOYER_OPTS) | |
| st.divider() | |
| # Section 2 β Financials | |
| st.markdown("<div class='ls-sec'>π° Financial Details</div>", | |
| unsafe_allow_html=True) | |
| b1,b2 = st.columns(2) | |
| with b1: | |
| app_inc = st.number_input("Applicant Income (βΉ/mo)", 0, 200_000, 8_000, 500) | |
| coapp_inc = st.number_input("Co-applicant Income (βΉ/mo)", 0, 100_000, 2_000, 500) | |
| savings = st.number_input("Savings Balance (βΉ)", 0, 500_000,15_000,1_000) | |
| with b2: | |
| cr_score = st.slider("Credit Score", 300, 900, 680) | |
| ex_loans = st.number_input("Existing Loans (#)", 0, 10, 1) | |
| dti = st.slider("DTI Ratio", 0.0, 1.0, 0.35, 0.01, | |
| help="Debt-to-Income ratio (0=no debt, 1=all income goes to debt)") | |
| band_lbl, band_ico = credit_band(cr_score) | |
| st.markdown( | |
| f"<span class='pill'>{band_ico} Credit: {cr_score} β {band_lbl}</span>" | |
| f"<span class='pill'>DTI: {dti:.0%}</span>" | |
| f"<span class='pill'>Existing Loans: {int(ex_loans)}</span>", | |
| unsafe_allow_html=True) | |
| st.divider() | |
| # Section 3 β Loan & Property | |
| st.markdown("<div class='ls-sec'>π¦ Loan & Property Details</div>", | |
| unsafe_allow_html=True) | |
| c1,c2 = st.columns(2) | |
| with c1: | |
| loan_amt = st.number_input("Loan Amount (βΉ)", 5_000,1_000_000,30_000,1_000) | |
| loan_term = st.number_input("Loan Term (months)", 12, 360, 84, 12) | |
| loan_purp = st.selectbox("Loan Purpose", PURPOSE_OPTS) | |
| with c2: | |
| collateral = st.number_input("Collateral Value (βΉ)", 0, 2_000_000, 50_000, 5_000) | |
| prop_area = st.selectbox("Property Area", PROPERTY_OPTS) | |
| ltv = round(loan_amt/collateral*100,1) if collateral>0 else 0 | |
| emi = round(loan_amt/loan_term,0) if loan_term>0 else 0 | |
| st.markdown( | |
| f"<span class='pill'>LTV: {ltv}%</span>" | |
| f"<span class='pill'>Est. EMI: {fmt(emi)}/mo</span>" | |
| f"<span class='pill'>Term: {int(loan_term)} mo</span>", | |
| unsafe_allow_html=True) | |
| st.markdown("<br>", unsafe_allow_html=True) | |
| st.markdown(f"<span class='pill-m'>Model: {short}</span>", | |
| unsafe_allow_html=True) | |
| predict_btn = st.button("π Predict Loan Eligibility", use_container_width=True) | |
| with col_r: | |
| # Result | |
| st.markdown("<div class='ls-sec'>π Prediction Result</div>", | |
| unsafe_allow_html=True) | |
| if predict_btn: | |
| raw = { | |
| "Applicant_Income": app_inc, | |
| "Coapplicant_Income": coapp_inc, | |
| "Employment_Status": emp_st, | |
| "Age": age, | |
| "Marital_Status": marital, | |
| "Dependents": dep, | |
| "Credit_Score": cr_score, | |
| "Existing_Loans": ex_loans, | |
| "DTI_Ratio": dti, | |
| "Savings": savings, | |
| "Collateral_Value": collateral, | |
| "Loan_Amount": loan_amt, | |
| "Loan_Term": loan_term, | |
| "Loan_Purpose": loan_purp, | |
| "Property_Area": prop_area, | |
| "Education_Level": edu, | |
| "Gender": gender, | |
| "Employer_Category": emp_cat, | |
| } | |
| model = load_model(sel_pkl) | |
| if model is None: | |
| st.error(f"**{sel_pkl}** not found. Upload pkl files via the Files tab.") | |
| else: | |
| try: | |
| X = preprocess(raw, scaler, encoder) | |
| pred = model.predict(X)[0] | |
| approved = int(pred) == 1 | |
| conf = (float(max(model.predict_proba(X)[0]))*100 | |
| if hasattr(model,"predict_proba") else 75.0) | |
| if approved: | |
| st.markdown( | |
| "<div class='res-ok'>" | |
| "<div class='res-ico'>β </div>" | |
| "<div class='res-title' style='color:#166534'>Loan Approved</div>" | |
| "<div class='res-sub'>Application meets eligibility criteria</div>" | |
| "</div>", unsafe_allow_html=True) | |
| else: | |
| st.markdown( | |
| "<div class='res-no'>" | |
| "<div class='res-ico'>β</div>" | |
| "<div class='res-title' style='color:#991B1B'>Loan Rejected</div>" | |
| "<div class='res-sub'>Application does not meet eligibility criteria</div>" | |
| "</div>", unsafe_allow_html=True) | |
| st.markdown(f"**Model Confidence** β `{conf:.1f}%`") | |
| st.progress(min(int(conf),100)) | |
| except Exception as e: | |
| st.error(f"Prediction error: {e}") | |
| st.caption("Tip: Re-upload scaler.pkl and encoder.pkl from the retrained artifacts.") | |
| else: | |
| st.markdown( | |
| "<div class='res-wait'>" | |
| "<div class='res-ico'>π</div>" | |
| "<div style='font-size:.86rem;margin-top:8px'>" | |
| "Fill in applicant details on the left<br>and click " | |
| "<b style='color:#2563A8'>Predict Loan Eligibility</b>" | |
| "</div></div>", unsafe_allow_html=True) | |
| st.divider() | |
| # Live Risk Scorecard | |
| st.markdown("<div class='ls-sec'>β‘ Live Risk Scorecard</div>", | |
| unsafe_allow_html=True) | |
| cr_pct = int((cr_score-300)/600*100) | |
| inc_pct = min(int(app_inc/200_000*100),100) | |
| sav_pct = min(int(savings/500_000*100),100) | |
| dti_pct = max(0,int((1-dti)*100)) | |
| coll_pct = min(int(collateral/loan_amt*100) if loan_amt else 0,100) | |
| for lbl,pct,cap in [ | |
| ("Credit Score", cr_pct, f"{cr_score} β {band_lbl}"), | |
| ("Income Level", inc_pct, fmt(app_inc)+"/mo"), | |
| ("Savings Buffer", sav_pct, fmt(savings)), | |
| ("Low DTI", dti_pct, f"{dti:.0%} DTI"), | |
| ("Collateral", coll_pct, f"LTV {ltv}%"), | |
| ]: | |
| st.markdown( | |
| f"<div class='sc-row'><span class='sc-lbl'>{lbl}</span>" | |
| f"<span class='sc-val'>{cap}</span></div>", | |
| unsafe_allow_html=True) | |
| st.progress(pct) | |
| with st.expander("π‘ Tips to improve eligibility"): | |
| st.markdown(""" | |
| - **Credit Score β₯ 700** dramatically improves approval odds | |
| - **DTI Ratio < 0.40** β clear existing loans before applying | |
| - **Collateral > Loan Amount** reduces lender risk | |
| - **Savings β₯ 3Γ EMI** signals financial stability | |
| - **Salaried + Government employer** scores highest | |
| - **Urban property area** has better approval rates | |
| """) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PAGE: INSIGHTS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| elif "Insights" in page: | |
| st.markdown("<div class='ls-sec'>π Credit Score Reference</div>", | |
| unsafe_allow_html=True) | |
| st.dataframe(pd.DataFrame({ | |
| "Band": ["Excellent (750β900)","Good (700β749)","Fair (650β699)","Poor (300β649)"], | |
| "Approval Rate %":[91,72,48,18], | |
| "Avg Interest %": [8.5,10.2,12.5,16.0], | |
| "Risk Level": ["Very Low","Low","Medium","High"], | |
| }), use_container_width=True, hide_index=True, | |
| column_config={"Approval Rate %": st.column_config.ProgressColumn( | |
| "Approval Rate", min_value=0, max_value=100, format="%d%%")}) | |
| st.divider() | |
| st.markdown("<div class='ls-sec'>π Dataset Overview</div>", | |
| unsafe_allow_html=True) | |
| m1,m2,m3,m4 = st.columns(4) | |
| m1.metric("Total Records","1,000") | |
| m2.metric("Raw Features","19") | |
| m3.metric("Approved","29.8%") | |
| m4.metric("Rejected","65.2%") | |
| st.caption("Class-imbalanced β F1 Score and Precision are primary metrics, not just accuracy.") | |
| st.divider() | |
| st.markdown("<div class='ls-sec'>π Key Approval Factors (from EDA)</div>", | |
| unsafe_allow_html=True) | |
| for ico,ttl,dsc in [ | |
| ("π―","Credit Score","Strongest predictor. Score β₯ 700 significantly boosts approval."), | |
| ("π°","DTI Ratio","Debt-to-income ratio. Below 0.40 strongly preferred by lenders."), | |
| ("π¦","Collateral Value","Higher collateral β lower lender risk β better approval odds."), | |
| ("πΌ","Employment Status","Salaried > Self-employed > Contract. Unemployed rarely approved."), | |
| ("π ","Property Area","Urban > Semiurban > Rural in approval likelihood."), | |
| ("π΅","Savings Balance","Higher savings signal resilience and repayment capacity."), | |
| ("π¨βπ©βπ§","Dependents","More dependents reduce disposable income β moderate negative impact."), | |
| ]: | |
| st.markdown( | |
| f"<div class='ins-row'><div class='ins-ico'>{ico}</div>" | |
| f"<div><div class='ins-ttl'>{ttl}</div>" | |
| f"<div class='ins-dsc'>{dsc}</div></div></div>", | |
| unsafe_allow_html=True) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PAGE: MODELS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| elif "Models" in page: | |
| st.markdown("<div class='ls-sec'>π€ Model Comparison</div>", | |
| unsafe_allow_html=True) | |
| st.caption("All models share the same pipeline β same scaler, same encoder, same 27 features.") | |
| st.dataframe(pd.DataFrame({ | |
| "Model": ["Logistic Regression β","K-Nearest Neighbours","Naive Bayes"], | |
| "File": ["model_lr.pkl","model_knn.pkl","model_nb.pkl"], | |
| "Accuracy": ["~87.5%","~75.5%","~86.5%"], | |
| "Precision": ["~79%","~62%","~78%"], | |
| "F1 Score": ["~80%","~56%","~78%"], | |
| "Best For": ["Default / overall","Pattern similarity","Minimising false approvals"], | |
| }), use_container_width=True, hide_index=True) | |
| st.divider() | |
| st.markdown("<div class='ls-sec'>π§ Preprocessing Pipeline (exact notebook)</div>", | |
| unsafe_allow_html=True) | |
| st.code( | |
| "Raw CSV (1000 rows Γ 20 cols)\n" | |
| " β\n" | |
| " ββ Impute: numerics=mean | categoricals=most_frequent\n" | |
| " ββ Drop: Applicant_ID\n" | |
| " ββ LabelEncode: Education_Level, Loan_Approved\n" | |
| " ββ OneHotEncode (drop=first):\n" | |
| " β Employment_Status, Marital_Status, Loan_Purpose,\n" | |
| " β Property_Area, Gender, Employer_Category\n" | |
| " ββ Feature Engineering (Cell 80):\n" | |
| " β DTI_Ratio_sq = DTI_Ratio ** 2\n" | |
| " β Credit_Score_sq = Credit_Score ** 2\n" | |
| " β # Applicant_Income_log β COMMENTED OUT (not used)\n" | |
| " ββ Drop originals: Credit_Score, DTI_Ratio\n" | |
| " ββ StandardScaler β 27 final features\n", | |
| language="text") | |
| st.divider() | |
| st.markdown("<div class='ls-sec'>π¦ Artifact Status</div>", | |
| unsafe_allow_html=True) | |
| for fname,desc in [ | |
| ("model_lr.pkl", "LogisticRegression() β Acc 87.5% Β· Prec 79%"), | |
| ("model_knn.pkl", "KNeighborsClassifier(n_neighbors=5) β Acc 75.5%"), | |
| ("model_nb.pkl", "GaussianNB() β Acc 86.5% Β· Best Precision"), | |
| ("scaler.pkl", "StandardScaler fitted on 27-feature X_train"), | |
| ("encoder.pkl", "OneHotEncoder(drop='first', sparse_output=False)"), | |
| ]: | |
| a,b,c = st.columns([2,3,1]) | |
| a.code(fname) | |
| b.caption(desc) | |
| c.markdown("β Loaded" if Path(fname).exists() else "β οΈ Missing") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PAGE: ABOUT | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| elif "About" in page: | |
| st.markdown("<div class='ls-sec'>βΉοΈ About LendSure AI</div>", | |
| unsafe_allow_html=True) | |
| st.markdown(""" | |
| **LendSure AI** is an end-to-end ML loan approval prediction system built as part of the | |
| AIML practitioner portfolio at **Anna University Regional Campus, Tirunelveli**. | |
| Three classifiers are trained on 1,000 real loan records through a full preprocessing pipeline | |
| and served via a transparent Streamlit UI with live risk scoring and model switching. | |
| | Layer | Technology | | |
| |-------|------------| | |
| | UI | Streamlit (HF Spaces) | | |
| | ML Models | Scikit-learn β LR Β· KNN Β· Naive Bayes | | |
| | Preprocessing | SimpleImputer + LabelEncoder + OHE + StandardScaler | | |
| | Feature Eng. | DTIΒ² Β· Credit ScoreΒ² | | |
| | Final Features | 27 columns (verified by retraining) | | |
| | Dataset | loan_approval_data.csv β 1,000 rows Β· 20 raw cols | | |
| | Version Control | Git LFS (pkl files) + GitHub | | |
| """) | |
| st.divider() | |
| st.markdown(""" | |
| **Author:** Karthika Krishna M β CSE, Anna University Regional Campus, Tirunelveli | |
| π [github.com/KARTHIKAKRISHNA123](https://github.com/KARTHIKAKRISHNA123) Β· | |
| π€ [KarthikaKrishna123 on Hugging Face](https://huggingface.co/KarthikaKrishna123) | |
| """) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FOOTER | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.markdown( | |
| "<div class='ls-foot'>LendSure AI Β· Built by <strong>Karthika Krishna M</strong> Β· " | |
| "<a href='https://github.com/KARTHIKAKRISHNA123' target='_blank'>GitHub</a> Β· " | |
| "Anna University Regional Campus, Tirunelveli | " | |
| "<em>For educational & demonstration purposes only</em></div>", | |
| unsafe_allow_html=True) |