Smart Lender: AI-Powered Loan Approval Prediction System ML Guide
Part 8: Frontend User Interface Files
To create a clean interface, we split our frontend assets into three files: index.html, style.css, and script.js.
8.1 How the Frontend Connects with the Backend
Web applications operate on a Client-Server Architecture.
- The Client (Frontend): This is the user interface running in the browser (
index.html,style.css,script.js). Its job is to collect information from the user and display results. - The Server (Backend): This is our Flask script (
app.py) running on our local machine. It listens for incoming HTTP requests, feeds features to our machine learning model, and returns the prediction. - The Communication (HTTP POST & JSON): When the user clicks the submit button, the frontend does not reload the page. Instead, JavaScript packages the form inputs into a JSON dictionary and sends it via an HTTP POST request to the
/predictURL on the Flask server. Once the server responds with a success or rejection state, the webpage updates itself dynamically. - Why split into HTML, CSS, and JS?: We separate structure (HTML), style (CSS), and behavior (JavaScript) to maintain clean code. This follows the Separation of Concerns principle, making it easy to redesign the UI or tweak frontend validations without editing backend code.
Frontend Template (templates/index.html)
Save this layout configuration inside templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smart Lender: Loan Approval Predictor</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<h2>Smart Lender Prediction</h2>
<form id="predictionForm">
<label for="dependents">Number of Dependents:</label>
<input type="number" id="dependents" min="0" value="0" required>
<label for="education">Education Level:</label>
<select id="education" required>
<option value="0">Graduate</option>
<option value="1">Not Graduate</option>
</select>
<label for="self_employed">Self Employed?</label>
<select id="self_employed" required>
<option value="0">No</option>
<option value="1">Yes</option>
</select>
<label for="income">Annual Income ($):</label>
<input type="number" id="income" min="0" required>
<label for="loan_amount">Requested Loan Amount ($):</label>
<input type="number" id="loan_amount" min="0" required>
<label for="loan_term">Repayment Term (Years):</label>
<input type="number" id="loan_term" min="1" max="30" required>
<label for="cibil_score">CIBIL / Credit Score (300 - 900):</label>
<input type="number" id="cibil_score" min="300" max="900" required>
<label for="residential_assets">Residential Assets Value ($):</label>
<input type="number" id="residential_assets" min="0" value="0" required>
<label for="commercial_assets">Commercial Assets Value ($):</label>
<input type="number" id="commercial_assets" min="0" value="0" required>
<label for="luxury_assets">Luxury Assets Value ($):</label>
<input type="number" id="luxury_assets" min="0" value="0" required>
<label for="bank_assets">Liquid Bank Assets Value ($):</label>
<input type="number" id="bank_assets" min="0" value="0" required>
<button type="submit">Check Loan Eligibility</button>
</form>
<div id="result">
<div id="statusHeader"></div>
<div id="probabilityText"></div>
</div>
</div>
<script src="/static/script.js"></script>
</body>
</html>
Style Sheet (static/style.css)
Save this CSS configuration inside static/style.css to build our form wrapper:
body {
background-color: #0a0a0c;
color: #e4e4e7;
font-family: system-ui, -apple-system, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px 0;
}
.container {
background-color: #121214;
border: 1px solid #27272a;
padding: 32px;
border-radius: 16px;
width: 380px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
}
h2 {
text-align: center;
margin-bottom: 24px;
font-size: 1.5rem;
color: #ffffff;
}
label {
display: block;
margin-bottom: 6px;
font-size: 0.85rem;
font-weight: 500;
color: #a1a1aa;
}
input, select, button {
width: 100%;
margin-bottom: 16px;
padding: 10px;
border-radius: 8px;
border: 1px solid #27272a;
background-color: #1a1a1e;
color: #ffffff;
box-sizing: border-box;
font-size: 0.9rem;
transition: all 0.2s;
}
input:focus, select:focus {
border-color: #3b82f6;
outline: none;
}
button {
background-color: #2563eb;
color: white;
border: none;
font-weight: 650;
cursor: pointer;
margin-top: 10px;
}
button:hover {
background-color: #1d4ed8;
}
#result {
margin-top: 15px;
padding: 16px;
border-radius: 8px;
text-align: center;
font-size: 0.95rem;
display: none;
}
.success {
background-color: rgba(16, 185, 129, 0.15);
border: 1px solid rgba(16, 185, 129, 0.4);
color: #34d399;
}
.danger {
background-color: rgba(239, 68, 68, 0.15);
border: 1px solid rgba(239, 68, 68, 0.4);
color: #f87171;
}
#statusHeader {
font-size: 1.1rem;
font-weight: 850;
margin-bottom: 8px;
}
#probabilityText {
font-size: 0.85rem;
opacity: 0.9;
}
Application Logic Script (static/script.js)
Save this JS module inside static/script.js:
document.getElementById('predictionForm').addEventListener('submit', async (e) => {
e.preventDefault();
const resultDiv = document.getElementById('result');
const statusHeader = document.getElementById('statusHeader');
const probText = document.getElementById('probabilityText');
resultDiv.style.display = 'none';
// Pack input form values into a JSON payload
const payload = {
dependents: document.getElementById('dependents').value,
education: document.getElementById('education').value,
self_employed: document.getElementById('self_employed').value,
income: document.getElementById('income').value,
loan_amount: document.getElementById('loan_amount').value,
loan_term: document.getElementById('loan_term').value,
cibil_score: document.getElementById('cibil_score').value,
residential_assets: document.getElementById('residential_assets').value,
commercial_assets: document.getElementById('commercial_assets').value,
luxury_assets: document.getElementById('luxury_assets').value,
bank_assets: document.getElementById('bank_assets').value
};
try {
// Send POST request containing payload to the prediction endpoint
const response = await fetch('/predict', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const result = await response.json();
resultDiv.style.display = 'block';
if (result.approved) {
resultDiv.className = 'success';
statusHeader.innerText = 'APPLICATION APPROVED';
probText.innerText = `Approval Confidence: ${result.approval_probability.toFixed(2)}% | Rejection Risk: ${result.rejection_probability.toFixed(2)}%`;
} else {
resultDiv.className = 'danger';
statusHeader.innerText = 'APPLICATION REJECTED';
probText.innerText = `Approval Confidence: ${result.approval_probability.toFixed(2)}% | Rejection Risk: ${result.rejection_probability.toFixed(2)}%`;
}
} catch (err) {
resultDiv.style.display = 'block';
resultDiv.className = 'danger';
statusHeader.innerText = 'Error processing eligibility.';
probText.innerText = '';
console.error(err);
}
});
8.2 Understanding the Script Execution
Let’s break down the logic of this script:
e.preventDefault(): Prevents the browser from reloading the page when you click the submit button. This allows us to handle the form processing silently in the background.payload: Gathers all values from form fields (e.g.incomeandcibil_score) and formats them into a single JavaScript object. The keys in this object match the keys in our Python request parser insideapp.py.fetch('/predict', ...): Starts a secure asynchronous HTTP connection. It sends the payload to our backend server as a JSON string (JSON.stringify(payload)) with headers identifying the content type.response.json(): Waits for the server to reply and parses the returned JSON string back into a readable JavaScript dictionary.- Result Banner Update: Displays the
#resultcontainer and applies the appropriate CSS styles. Ifresult.approvedistrue, it displays a green success banner; iffalse, it shows a red rejection banner.
8.3 Executing the Model Training Script
Before our server can process predictions, we must generate our serialized model and scaler weights:
- Open your terminal in the root of your
smart_lenderdirectory. - Execute the training pipeline:
python train.py - Inspect the Console Outputs: You will see step-by-step progress as Scikit-Learn cleans headers, splits train/test subsets, applies standard scaling, compares accuracies, selects the winner, and confirms serialization:
Step 1: Loading loan application dataset... Step 2: Cleaning whitespace columns and values... Step 3: Categorical Label Encoding... Step 4: Applying Standard Scaling to features... Step 5: Training and comparing classification models... -> Logistic Regression Test Accuracy: 89.20% -> Random Forest Test Accuracy: 97.80% -> XGBoost Test Accuracy: 98.40% Winner Selected: XGBoost with 98.40% accuracy. Step 6: Serializing and saving best model & scaler... ✓ Output model: models/loan_model.joblib ✓ Output scaler: models/scaler.joblib - Verify Output Files: Confirm that
loan_model.joblibandscaler.joblibhave been successfully created inside yourmodels/directory.
8.4 Running the Flask Web Server
With the model weights and all frontend/backend files created, we can spin up our local development web server:
- Rerun the Flask server in your terminal:
python app.py - Confirm Server Initialization: The console will output:
✓ Model and Scaler loaded successfully! * Serving Flask app 'app' * Debug mode: on * Running on http://127.0.0.1:5000 - Access the Portal: Open your web browser and navigate to:
You should see your newly created Loan Eligibility form displayed.http://localhost:5000
8.5 Manual Verification & Test Scenarios
To test that your machine learning model and web interface are communicating correctly, try inputting these two validation profiles:
- Test Scenario 1: Low-Risk Approved Loan
- Dependents:
0 - Education Level:
Graduate(select from dropdown) - Self Employed:
No(select from dropdown) - Annual Income:
8,000,000(8 Million) - Requested Loan:
1,000,000(1 Million) - Term:
5Years - CIBIL / Credit Score:
780 - Residential Assets:
3,000,000 - Commercial Assets:
2,000,000 - Luxury Assets:
1,500,000 - Liquid Bank Assets:
500,000 - Expected Result: APPLICATION APPROVED banner displays with high confidence.
- Dependents:
- Test Scenario 2: High-Risk Rejected Loan
- Dependents:
4 - Education Level:
Not Graduate(select from dropdown) - Self Employed:
Yes(select from dropdown) - Annual Income:
200,000 - Requested Loan:
8,000,000(8 Million) - Term:
20Years - CIBIL / Credit Score:
350 - Residential Assets:
0 - Commercial Assets:
0 - Luxury Assets:
0 - Liquid Bank Assets:
5,000 - Expected Result: APPLICATION REJECTED banner displays with high confidence.
- Dependents:
8.6 Critical Fallbacks & Common Run Issues
As a developer, you may encounter these common startup errors. Here is how to diagnose and resolve them:
- Error: Address already in use (Port Conflict): If you see a crash saying
OSError: [Errno 98] Address already in use, it means another service on your computer is already listening on port 5000 (such as macOS AirPlay Receiver, or an orphaned Python task running in the background).- The Fix: Open
app.py, scroll to the very bottom, and change the port configuration to5001or8080:
Then, rerunapp.run(port=5001, debug=True)python app.pyand navigate tohttp://localhost:5001.
- The Fix: Open
- Error: TemplateNotFound: If you navigate to the page and see a Flask traceback error stating
jinja2.exceptions.TemplateNotFound: index.html, it means your HTML file is in the wrong directory.- The Fix: Double check your workspace sidebar. Ensure
index.htmlresides inside a folder namedtemplates. If it is in the root or insidestatic, Flask’s engine will not find it.
- The Fix: Double check your workspace sidebar. Ensure