Skip to content

Commit d814581

Browse files
committed
feat: Add Streamlit app for interactive loan applications
- Introduced a new Streamlit application to facilitate interactive loan applications using the trained credit scoring model. - Updated README to include instructions for running the Streamlit app and added a demo image. - Enhanced pyproject.toml to include Streamlit as an optional dependency. This addition allows users to visualize and interact with the credit scoring model in a user-friendly manner. Signed-off-by: D. Danchev <[email protected]>
1 parent eb39cae commit d814581

File tree

5 files changed

+782
-3
lines changed

5 files changed

+782
-3
lines changed

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ podman run -d -p 6379:6379 --name redis -e "ALLOW_EMPTY_PASSWORD=yes" docker.io
3535
Install Feast using uv
3636

3737
```
38-
uv sync
38+
uv sync --all-extras
3939
```
4040

4141
We have already set up a feature repository in [feature_repo/](feature_repo/). As a result, all we have to do is configure the [feature_store.yaml/](feature_repo/feature_store.yaml) in the feature repository. Please set the connection string of the Postgresql and Redis according to your local infra setup.
@@ -248,4 +248,17 @@ Example returned feature values:
248248
}
249249
]
250250
}
251-
```
251+
```
252+
253+
254+
## Interactive demo (using Streamlit)
255+
256+
Once the credit scoring model has been trained it can be used for interactive loan applications using Streamlit:
257+
258+
Simply start the Streamlit application
259+
```
260+
uv run streamlit run streamlit_app.py
261+
```
262+
Then navigate to the URL on which Streamlit is being served. You should see a user interface through which loan applications can be made:
263+
264+
![Streamlit Loan Application](streamlit.png)

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ dependencies = [
1414
"grpcio-tools",
1515
"grpcio-reflection",
1616
]
17+
[project.optional-dependencies]
18+
streamlit = [
19+
"streamlit>=1.50.0",
20+
"shap>=0.49.0",
21+
"matplotlib>=3.10.7",
22+
]

streamlit.png

239 KB
Loading

streamlit_app.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import datetime
2+
import logging
3+
import sys
4+
from collections import OrderedDict
5+
6+
import pandas as pd
7+
import shap
8+
import streamlit as st
9+
from matplotlib import pyplot as plt
10+
11+
from credit_model import CreditScoringModel
12+
13+
logging.basicConfig(
14+
level=logging.DEBUG,
15+
format="%(asctime)s %(levelname)s %(message)s",
16+
handlers=[logging.StreamHandler(sys.stdout)],
17+
)
18+
19+
logging.debug("Streamlit app started.")
20+
21+
st.set_page_config(layout="wide")
22+
model = CreditScoringModel()
23+
if not model.is_model_trained():
24+
raise Exception("The credit scoring model has not been trained. Please run run.py.")
25+
26+
27+
def get_loan_request():
28+
zipcode = st.sidebar.text_input("Zip code", "94109")
29+
date_of_birth = st.sidebar.date_input(
30+
"Date of birth", value=datetime.date(year=1986, day=19, month=3)
31+
)
32+
ssn_last_four = st.sidebar.text_input(
33+
"Last four digits of social security number", "3643"
34+
)
35+
dob_ssn = f"{date_of_birth.strftime('%Y%m%d')}_{str(ssn_last_four)}"
36+
age = st.sidebar.slider("Age", 0, 130, 25)
37+
income = st.sidebar.slider("Yearly Income", 0, 1000000, 120000)
38+
person_home_ownership = st.sidebar.selectbox(
39+
"Do you own or rent your home?", ("RENT", "MORTGAGE", "OWN")
40+
)
41+
42+
employment = st.sidebar.slider(
43+
"How long have you been employed (months)?", 0, 120, 12
44+
)
45+
46+
loan_intent = st.sidebar.selectbox(
47+
"Why do you want to apply for a loan?",
48+
(
49+
"PERSONAL",
50+
"VENTURE",
51+
"HOMEIMPROVEMENT",
52+
"EDUCATION",
53+
"MEDICAL",
54+
"DEBTCONSOLIDATION",
55+
),
56+
)
57+
58+
amount = st.sidebar.slider("Loan amount", 0, 100000, 10000)
59+
interest = st.sidebar.slider("Preferred interest rate", 1.0, 25.0, 12.0, step=0.1)
60+
return OrderedDict(
61+
{
62+
"zipcode": [int(zipcode)],
63+
"dob_ssn": [dob_ssn],
64+
"person_age": [age],
65+
"person_income": [income],
66+
"person_home_ownership": [person_home_ownership],
67+
"person_emp_length": [float(employment)],
68+
"loan_intent": [loan_intent],
69+
"loan_amnt": [amount],
70+
"loan_int_rate": [interest],
71+
}
72+
)
73+
74+
75+
# Application
76+
st.title("Loan Application")
77+
78+
# Input Side Bar
79+
st.header("User input:")
80+
loan_request = get_loan_request()
81+
df = pd.DataFrame.from_dict(loan_request)
82+
83+
logging.debug(f"User input: {loan_request}")
84+
st.write(df)
85+
86+
# Full feature vector
87+
st.header("Feature vector (user input + zipcode features + user features):")
88+
vector = model._get_online_features_from_feast(loan_request)
89+
ordered_vector = loan_request.copy()
90+
key_list = vector.keys()
91+
key_list = sorted(key_list)
92+
for vector_key in key_list:
93+
if vector_key not in ordered_vector:
94+
ordered_vector[vector_key] = vector[vector_key]
95+
df = pd.DataFrame.from_dict(ordered_vector)
96+
97+
logging.debug(f"Online features from Feast: {vector}")
98+
st.write(df)
99+
100+
# Results of prediction
101+
st.header("Application Status (model prediction):")
102+
result = model.predict(loan_request)
103+
104+
if result == 0:
105+
st.success("Your loan has been approved!")
106+
elif result == 1:
107+
st.error("Your loan has been rejected!")
108+
109+
logging.debug(f"Model prediction result: {result}")
110+
111+
# Feature importance
112+
st.header("Feature Importance")
113+
# TODO: Load a sample dataset from feature store instead of a static file
114+
X = pd.read_parquet("data/training_dataset_sample.parquet")
115+
X['total_debt_due'] = (X['credit_card_due'] + X['mortgage_due'] + X['student_loan_due'] + X['vehicle_loan_due'] + X['loan_amnt']).astype(float)
116+
explainer = shap.TreeExplainer(model.classifier)
117+
shap_values = explainer.shap_values(X)
118+
left, right = st.columns(2)
119+
with left:
120+
fig, ax = plt.subplots()
121+
plt.title("Feature importance based on SHAP values")
122+
shap.summary_plot(shap_values[:,:,1], X) # Select only the values for class 1
123+
st.pyplot(fig, bbox_inches="tight")
124+
st.write("---")
125+
126+
with right:
127+
fig, ax = plt.subplots()
128+
plt.title("Feature importance based on SHAP values (Bar)")
129+
shap.summary_plot(shap_values[:,:,1], X, plot_type="bar") # Select only the values for class 1
130+
st.pyplot(fig, bbox_inches="tight")
131+
132+
logging.debug("Streamlit app finished.")

0 commit comments

Comments
 (0)