SmartLoanAI / app.py
ifiecas's picture
Update app.py
8c657e8 verified
raw
history blame
13.9 kB
import streamlit as st
import joblib
import numpy as np
from huggingface_hub import hf_hub_download
# Page configuration
st.set_page_config(
page_title="Loan Approval System",
page_icon="🏦",
layout="centered",
initial_sidebar_state="collapsed"
)
# Custom CSS for styling with the specified color theme
st.markdown("""
<style>
/* Color Theme */
:root {
--primary-purple: #7950F2;
--primary-purple-light: #9775F3;
--primary-purple-dark: #5F3DC4;
--complementary-orange: #FF5E3A;
--complementary-orange-light: #FF8A6C;
--light-gray: #F8F9FA;
--dark-gray: #343A40;
}
/* Main containers */
.main .block-container {
padding: 2rem;
border-radius: 10px;
background-color: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
/* Font family - applied globally */
* {
font-family: 'Helvetica', 'Arial', sans-serif !important;
}
/* Specific selectors to ensure Helvetica is applied everywhere */
body, .stMarkdown, p, h1, h2, h3, h4, h5, h6, .stButton, .stSelectbox, .stNumberInput,
.stTextInput, div, span, .streamlit-container, .stAlert, .stText, button, input, select,
textarea, .streamlit-expanderHeader, .streamlit-expanderContent {
font-family: 'Helvetica', 'Arial', sans-serif !important;
}
/* Headers */
h1, h2, h3 {
color: var(--primary-purple-dark);
}
/* Custom cards for sections */
.section-card {
background-color: var(--light-gray);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
border-left: 4px solid var(--primary-purple);
}
/* Button styling */
.stButton > button {
background-color: var(--primary-purple);
color: white;
border: none;
border-radius: 5px;
padding: 0.5rem 1rem;
font-weight: bold;
width: 100%;
transition: all 0.3s;
}
.stButton > button:hover {
background-color: var(--primary-purple-dark);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* Result styling */
.result-approved {
background-color: #E8F5E9;
border-left: 4px solid #4CAF50;
padding: 1rem;
border-radius: 5px;
margin-top: 1rem;
}
.result-rejected {
background-color: #FFEBEE;
border-left: 4px solid #F44336;
padding: 1rem;
border-radius: 5px;
margin-top: 1rem;
}
/* Input widgets */
.stNumberInput, .stSelectbox {
margin-bottom: 1rem;
}
/* Footer */
.footer {
text-align: center;
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #EEEEEE;
font-size: 0.8rem;
color: #666666;
}
/* Divider */
.divider {
border-top: 1px solid #EEEEEE;
margin: 1.5rem 0;
}
/* Badge */
.badge {
display: inline-block;
background-color: var(--complementary-orange);
color: white;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
margin-left: 0.5rem;
}
</style>
""", unsafe_allow_html=True)
# App header with logo
col1, col2 = st.columns([1, 5])
with col1:
st.markdown('<div style="text-align: center; padding: 10px;"><span style="font-size: 40px;">🏦</span></div>', unsafe_allow_html=True)
with col2:
st.title("AI-Powered Loan Approval System")
st.markdown('<p style="color: #666;">Fast and reliable loan approval decisions</p>', unsafe_allow_html=True)
# Load the trained model from Hugging Face
@st.cache_resource
def load_model():
model_path = hf_hub_download(repo_id="ifiecas/LoanApproval-DT-v1.0", filename="best_pruned_dt.pkl")
return joblib.load(model_path)
model = load_model()
# Initialize session state for restart functionality
if 'restart_clicked' not in st.session_state:
st.session_state.restart_clicked = False
# Create tabs for better organization
tab1, tab2 = st.tabs(["Loan Application", "About the System"])
with tab1:
# Reset all form values if restart was clicked
if st.session_state.restart_clicked:
st.session_state.restart_clicked = False # Reset flag
# Personal Information Section
st.markdown('<div class="section-card"><h3>Personal Information</h3>', unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
gender = st.selectbox("Gender", ["Male", "Female"])
education = st.selectbox("Education Level", ["Graduate", "Under Graduate"])
with col2:
marital_status = st.selectbox("Marital Status", ["Married", "Not Married"])
number_of_dependents = st.number_input("Number of Dependents", min_value=0, max_value=10, value=0)
self_employed = st.selectbox("Self-Employed", ["No", "Yes"])
st.markdown('</div>', unsafe_allow_html=True)
# Financial Details Section
st.markdown('<div class="section-card"><h3>Financial Details</h3>', unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
applicant_income = st.number_input("Monthly Income ($)", min_value=0, value=5000)
loan_amount = st.number_input("Loan Amount ($)", min_value=0, value=100000)
credit_history = st.selectbox("Credit History Status", [1, 0],
format_func=lambda x: "No existing unsettled loans (1)" if x == 1 else "Have unsettled loans (0)")
with col2:
coapplicant_income = st.number_input("Co-Applicant's Income ($)", min_value=0)
loan_term = st.slider("Loan Term (months)", min_value=12, max_value=360, value=180, step=12)
location = st.selectbox("Property Location", ["Urban", "Semiurban", "Rural"])
st.markdown('</div>', unsafe_allow_html=True)
# Summary section - without DTI Assessment or Eligibility Check
st.markdown('<div class="section-card"><h3>Application Summary</h3>', unsafe_allow_html=True)
total_income = applicant_income + coapplicant_income
# Calculate monthly payment (simplified calculation)
interest_rate = 0.05 # Assuming 5% annual interest rate
monthly_interest = interest_rate / 12
num_payments = loan_term
# Monthly payment using the loan amortization formula
if monthly_interest == 0 or num_payments == 0:
monthly_payment = 0
else:
monthly_payment = loan_amount * (monthly_interest * (1 + monthly_interest) ** num_payments) / \
((1 + monthly_interest) ** num_payments - 1)
# Calculate DTI for backend use only (not displayed)
dti = (monthly_payment / total_income) if total_income > 0 else 0
dti_percent = dti * 100
# Display summary metrics
col1, col2, col3 = st.columns(3)
col1.metric("Total Monthly Income", f"${total_income:,}")
col2.metric("Estimated Monthly Payment", f"${monthly_payment:.2f}")
col3.metric("Loan Term", f"{loan_term//12} years")
# Add interest rate disclaimer
st.markdown(f"""
<div style="font-size: 0.8rem; color: #666; margin-top: -10px; margin-bottom: 20px;">
* Estimated payment based on {interest_rate*100:.1f}% annual interest rate. Actual rates may vary.
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Prediction and restart buttons
col1, col2 = st.columns([3, 1])
with col1:
predict_button = st.button("Check Loan Approval Status", use_container_width=True)
with col2:
restart_button = st.button("🔄 Restart", use_container_width=True,
help="Reset all form fields and start over")
# Handle restart button click
if restart_button:
st.session_state.restart_clicked = True
st.rerun() # Using st.rerun() instead of st.experimental_rerun()
def preprocess_input():
# Convert categorical inputs to numerical format based on encoding reference
gender_num = 0 if gender == "Male" else 1
marital_status_num = 0 if marital_status == "Not Married" else 1
education_num = 0 if education == "Under Graduate" else 1
self_employed_num = 0 if self_employed == "No" else 1
credit_history_num = credit_history # Already numerical (0,1)
# One-Hot Encoding for Location
location_semiurban = 1 if location == "Semiurban" else 0
location_urban = 1 if location == "Urban" else 0
# Convert Term from months to years
term_years = loan_term / 12
# Compute Derived Features - use the same monthly payment calculated above
debt_to_income = monthly_payment / total_income if total_income > 0 else 0
credit_amount_interaction = loan_amount * credit_history_num # Interaction effect
income_term_ratio = total_income / term_years if term_years > 0 else 0 # Avoid divide by zero
# Return array with all 16 features
return np.array([[
gender_num, marital_status_num, number_of_dependents, education_num, self_employed_num,
applicant_income, coapplicant_income, loan_amount, credit_history_num,
total_income, debt_to_income, location_semiurban, location_urban, term_years,
credit_amount_interaction, income_term_ratio
]])
# Display prediction
if predict_button:
with st.spinner("Processing your application..."):
input_data = preprocess_input()
prediction = model.predict(input_data)
# Apply additional rules to override the model in certain cases (backend only)
manual_rejection = False
# Rule-based rejections that override the model (but don't show to user)
if total_income < 1500:
manual_rejection = True
elif dti_percent > 50:
manual_rejection = True
elif credit_history == 0 and dti_percent > 35:
manual_rejection = True
# Final decision combines model prediction and manual eligibility checks
final_approval = (prediction[0] == 1) and not manual_rejection
# Show result with enhanced styling
if final_approval:
st.markdown("""
<div class="result-approved">
<h3 style="color: #2E7D32;">✅ Loan Approved</h3>
<p>Congratulations! Based on your information, you're eligible for this loan.</p>
</div>
""", unsafe_allow_html=True)
else:
st.markdown("""
<div class="result-rejected">
<h3 style="color: #C62828;">❌ Loan Not Approved</h3>
<p>Unfortunately, based on your current information, we cannot approve your loan application.</p>
<p>Consider improving your credit score, reducing existing debt, or applying with a co-applicant with higher income.</p>
</div>
""", unsafe_allow_html=True)
with tab2:
st.markdown("""
<div class="section-card">
<h3>About the Loan Approval System</h3>
<p>This AI-powered system uses advanced machine learning algorithms to determine loan approval eligibility based on multiple factors.</p>
</div>
""", unsafe_allow_html=True)
st.markdown("<h4>How it works</h4>", unsafe_allow_html=True)
st.write("The system analyzes various factors including:")
st.markdown("""
- Personal and financial information
- Credit history status
- Loan amount and term
- Income and employment status
""")
st.write("""
Our decision engine combines a trained machine learning model with industry-standard lending criteria.
The system evaluates your application against patterns from thousands of previous loan applications.
""")
st.markdown('<div class="section-card">', unsafe_allow_html=True)
st.markdown("<h3>About the ML Model</h3>", unsafe_allow_html=True)
st.write("""
The machine learning model used here is a Decision Tree, which was the best performing model among KNN,
Random Forest, Logistic Regression, and Support Vector Machine models tested.
The Decision Tree model was further refined using Cost Complexity Pruning (CCP) to find the optimal
alpha value for pruning, which helps prevent overfitting while maintaining high accuracy.
""")
st.markdown("<h4>Model Performance Metrics:</h4>", unsafe_allow_html=True)
st.markdown("""
- Accuracy: 0.8361
- Precision: 0.8077
- Recall: 1.0000
- F1 Score: 0.8936
""")
st.markdown("""
<p>For more information about the modeling process (from loading the dataset to fine-tuning the model),
check here: <a href="#" style="color: #7950F2;">[Coming Soon]</a></p>
<p style="margin-top: 20px;">Enjoyed doing this and learned a lot! 😊</p>
<h3 style="margin-top: 30px;">Behind the Build</h3>
<p><a href="https://ifiecas.com/" target="_blank" style="color: #7950F2; text-decoration: none;">Ivy Fiecas-Borjal</a></p>
<p style="font-size: 0.9rem;">Inspired by an assessment in BCO6008 Predictive Analytics class in Victoria University (Melbourne) with Dr. Omid Ameri Sianaki</p>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Footer
st.markdown("""
<div class="footer">
<p>© 2025 AI-Powered Loan Approval System | <a href="#" style="color: #7950F2;">Terms of Service</a> | <a href="#" style="color: #7950F2;">Privacy Policy</a></p>
</div>
""", unsafe_allow_html=True)