aadhavans commited on
Commit
c0cf317
·
1 Parent(s): 009b0b9

Fixing Dockerfile and adding files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .streamlit/config.toml +15 -0
  2. Dockerfile +11 -6
  3. KPI_Dashboard.py +470 -0
  4. README.md +1 -8
  5. SHAP_plots/SHAP_Plot_CUST0001.jpg +3 -0
  6. SHAP_plots/SHAP_Plot_CUST0002.jpg +3 -0
  7. SHAP_plots/SHAP_Plot_CUST0003.jpg +3 -0
  8. SHAP_plots/SHAP_Plot_CUST0004.jpg +3 -0
  9. SHAP_plots/SHAP_Plot_CUST0005.jpg +3 -0
  10. SHAP_plots/SHAP_Plot_CUST0006.jpg +3 -0
  11. SHAP_plots/SHAP_Plot_CUST0007.jpg +3 -0
  12. SHAP_plots/SHAP_Plot_CUST0008.jpg +3 -0
  13. SHAP_plots/SHAP_Plot_CUST0009.jpg +3 -0
  14. SHAP_plots/SHAP_Plot_CUST0010.jpg +3 -0
  15. SHAP_plots/SHAP_Plot_CUST0011.jpg +3 -0
  16. SHAP_plots/SHAP_Plot_CUST0012.jpg +3 -0
  17. SHAP_plots/SHAP_Plot_CUST0013.jpg +3 -0
  18. SHAP_plots/SHAP_Plot_CUST0014.jpg +3 -0
  19. SHAP_plots/SHAP_Plot_CUST0015.jpg +3 -0
  20. SHAP_plots/SHAP_Plot_CUST0016.jpg +3 -0
  21. SHAP_plots/SHAP_Plot_CUST0017.jpg +3 -0
  22. SHAP_plots/SHAP_Plot_CUST0018.jpg +3 -0
  23. SHAP_plots/SHAP_Plot_CUST0019.jpg +3 -0
  24. SHAP_plots/SHAP_Plot_CUST0020.jpg +3 -0
  25. SHAP_plots/SHAP_Plot_CUST0021.jpg +3 -0
  26. SHAP_plots/SHAP_Plot_CUST0022.jpg +3 -0
  27. SHAP_plots/SHAP_Plot_CUST0023.jpg +3 -0
  28. SHAP_plots/SHAP_Plot_CUST0024.jpg +3 -0
  29. SHAP_plots/SHAP_Plot_CUST0025.jpg +3 -0
  30. SHAP_plots/SHAP_Plot_CUST0026.jpg +3 -0
  31. SHAP_plots/SHAP_Plot_CUST0027.jpg +3 -0
  32. SHAP_plots/SHAP_Plot_CUST0028.jpg +3 -0
  33. SHAP_plots/SHAP_Plot_CUST0029.jpg +3 -0
  34. SHAP_plots/SHAP_Plot_CUST0030.jpg +3 -0
  35. SHAP_plots/SHAP_Plot_CUST0031.jpg +3 -0
  36. SHAP_plots/SHAP_Plot_CUST0032.jpg +3 -0
  37. SHAP_plots/SHAP_Plot_CUST0033.jpg +3 -0
  38. SHAP_plots/SHAP_Plot_CUST0034.jpg +3 -0
  39. SHAP_plots/SHAP_Plot_CUST0035.jpg +3 -0
  40. SHAP_plots/SHAP_Plot_CUST0036.jpg +3 -0
  41. SHAP_plots/SHAP_Plot_CUST0037.jpg +3 -0
  42. SHAP_plots/SHAP_Plot_CUST0038.jpg +3 -0
  43. SHAP_plots/SHAP_Plot_CUST0039.jpg +3 -0
  44. SHAP_plots/SHAP_Plot_CUST0040.jpg +3 -0
  45. SHAP_plots/SHAP_Plot_CUST0041.jpg +3 -0
  46. SHAP_plots/SHAP_Plot_CUST0042.jpg +3 -0
  47. SHAP_plots/SHAP_Plot_CUST0043.jpg +3 -0
  48. SHAP_plots/SHAP_Plot_CUST0044.jpg +3 -0
  49. SHAP_plots/SHAP_Plot_CUST0045.jpg +3 -0
  50. SHAP_plots/SHAP_Plot_CUST0046.jpg +3 -0
.streamlit/config.toml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [server]
2
+ # The port where the server will listen for browser connections.
3
+ port = 8501
4
+ # Enable static file serving (set to true if you want to serve static files).
5
+ enableStaticServing = false
6
+
7
+ [theme]
8
+ base = "dark"
9
+ primaryColor = "#8D1DB9"
10
+ backgroundColor = "#191414"
11
+ secondaryBackgroundColor = "#282828"
12
+ font = "sans-serif"
13
+ headingFont = "sans-serif"
14
+ codeFont = "monospace"
15
+ textColor = "#ffffff"
Dockerfile CHANGED
@@ -2,20 +2,25 @@ FROM python:3.9-slim
2
 
3
  WORKDIR /app
4
 
 
5
  RUN apt-get update && apt-get install -y \
6
  build-essential \
7
  curl \
8
- software-properties-common \
9
  git \
10
- && rm -rf /var/lib/apt/lists/*
11
 
 
12
  COPY requirements.txt ./
13
- COPY src/ ./src/
14
 
15
- RUN pip3 install -r requirements.txt
 
16
 
 
17
  EXPOSE 8501
18
 
19
- HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
 
20
 
21
- ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
 
2
 
3
  WORKDIR /app
4
 
5
+ # Install OS-level dependencies (removed software-properties-common)
6
  RUN apt-get update && apt-get install -y \
7
  build-essential \
8
  curl \
 
9
  git \
10
+ && rm -rf /var/lib/apt/lists/*
11
 
12
+ # Copy dependency list and install
13
  COPY requirements.txt ./
14
+ RUN pip3 install --no-cache-dir -r requirements.txt
15
 
16
+ # Copy the rest of your project into the container
17
+ COPY . .
18
 
19
+ # Expose Streamlit's default port
20
  EXPOSE 8501
21
 
22
+ # Healthcheck for Spaces
23
+ HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health || exit 1
24
 
25
+ # Run your main app file
26
+ ENTRYPOINT ["streamlit", "run", "KPI_Dashboard.py", "--server.port=8501", "--server.address=0.0.0.0"]
KPI_Dashboard.py ADDED
@@ -0,0 +1,470 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import plotly.graph_objects as go
4
+ from streamlit_extras.metric_cards import style_metric_cards
5
+ import json
6
+ import warnings
7
+
8
+ warnings.filterwarnings("ignore")
9
+
10
+ # -----------------------------
11
+ # Page Configuration
12
+ # -----------------------------
13
+ st.set_page_config(
14
+ page_title="KPI Dashboard",
15
+ layout="wide",
16
+ initial_sidebar_state="expanded",
17
+ )
18
+
19
+ # -----------------------------
20
+ # Inject Custom CSS
21
+ # -----------------------------
22
+ with open("assets/styles.css", encoding="utf-8") as f:
23
+ st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
24
+
25
+ # -----------------------------
26
+ # Styling Metric Cards
27
+ # -----------------------------
28
+ style_metric_cards(
29
+ background_color="#141212",
30
+ border_color="#8D1DB9",
31
+ border_size_px=1,
32
+ border_radius_px=10,
33
+ border_left_color="#8D1DB9",
34
+ box_shadow=True
35
+ )
36
+
37
+ st.markdown("""
38
+ <style>
39
+ /* increase the size of the value and the label in the metric cards */
40
+ [data-testid="stMetricLabel"] p {
41
+ font-size: 26px !important;
42
+ font-weight: 300 !important;
43
+ color: white !important;
44
+ }
45
+
46
+ [data-testid="stMetricValue"] {
47
+ font-size: 50px;
48
+ }
49
+ </style>
50
+ """, unsafe_allow_html=True)
51
+
52
+ # -----------------------------
53
+ # Header Section
54
+ # -----------------------------
55
+ col_title, col_logo = st.columns([4, 1])
56
+ with col_title:
57
+ st.markdown("""
58
+ <style>
59
+ .stImage > img {
60
+ width: 200px; /* Increase size */
61
+ float: right; /* Align to the right */
62
+ border-radius: 10px;
63
+ box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2);
64
+ transition: transform 0.3s ease-in-out;
65
+ }
66
+ </style>
67
+ """, unsafe_allow_html=True)
68
+ st.image("assets/bank-logo.png", width=90)
69
+
70
+ with col_logo:
71
+ st.markdown("""
72
+ <style>
73
+ .stImage > img {
74
+ width: 200px; /* Increase size */
75
+ float: right; /* Align to the right */
76
+ border-radius: 10px;
77
+ box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2);
78
+ transition: transform 0.3s ease-in-out;
79
+ }
80
+ </style>
81
+ """, unsafe_allow_html=True)
82
+ st.image("assets/ailabs_logo.png", width=250)
83
+
84
+ col_title, col_loan_line = st.columns([4, 1])
85
+ with col_title:
86
+ st.markdown(
87
+ "<h1 style='text-align: left; color: #ffffff;'>Early Detection Dashboard for Credit Risk</h1>",
88
+ unsafe_allow_html=True
89
+ )
90
+
91
+ with col_loan_line:
92
+ st.markdown("""
93
+ <style>
94
+ .stImage > img {
95
+ width: 200px; /* Increase size */
96
+ float: right; /* Align to the right */
97
+ border-radius: 10px;
98
+ box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2);
99
+ transition: transform 0.3s ease-in-out;
100
+ }
101
+ </style>
102
+ """, unsafe_allow_html=True)
103
+ st.image("assets/loan_line.png", width=350)
104
+
105
+ st.subheader("Product Type: :violet[Personal Loans]")
106
+ st.markdown("---")
107
+
108
+ # -----------------------------
109
+ # Define Risk Score Bands
110
+ # -----------------------------
111
+ data = {
112
+ "Risk Level": ["High Risk", "Medium Risk", "Low Risk"],
113
+ "Score Range": ["0 - 350", "351 - 650", "651 - 999"],
114
+ "Description": [
115
+ "High probability of risk.",
116
+ "Moderate probability of risk.",
117
+ "Low probability of risk."
118
+ ]
119
+ }
120
+ df_risk_bands = pd.DataFrame(data)
121
+
122
+ def highlight_risk(row):
123
+ color = ''
124
+ if row['Risk Level'] == 'High Risk':
125
+ color = 'background-color: #BD100D; color: white;'
126
+ elif row['Risk Level'] == 'Medium Risk':
127
+ color = 'background-color: #B88F12; color: white;'
128
+ elif row['Risk Level'] == 'Low Risk':
129
+ color = 'background-color: #3A800F; color: white;'
130
+ return [color]*len(row)
131
+
132
+ df_risk_bands = df_risk_bands.style.apply(highlight_risk, axis=1)
133
+
134
+ # -----------------------------
135
+ # Load Portfolio Data
136
+ # -----------------------------
137
+ def load_data():
138
+ # df = pd.read_csv("data/time_series_customer_loan_data_with_shap.csv")
139
+ df = pd.read_csv("data/kenya_personal_loan_data_with_shap.csv")
140
+ df["Top Contributors"] = df["Top Contributors"].apply(json.loads)
141
+ df['Date'] = pd.to_datetime(df['Date'])
142
+ # df['Loan Status'] = df['Delinquency Bucket'].apply(lambda x: 'Current' if x in [0, 1] else 'Delinquent') # defining the delinquency status
143
+ return df
144
+
145
+ df = load_data()
146
+
147
+ target_date = '2026-01-31'
148
+ last_month_df = df[df['Date'] == target_date] # filter the dataframe for the target_date
149
+
150
+ # -----------------------------
151
+ # Compute KPIs
152
+ # -----------------------------
153
+
154
+ # Portfolio Composition
155
+ total_number_of_accounts = df['Account ID'].nunique()
156
+ total_ending_balance = df["Outstanding Balance"].sum()
157
+ average_loan_size = df["Initial Loan Amount"].mean()
158
+
159
+ payments_sum_by_date = df.groupby('Date')['Monthly Payments'].sum().reset_index()
160
+ payments_sum_by_date = df.groupby('Date')['Monthly Payments'].sum().reset_index()
161
+ match = payments_sum_by_date[payments_sum_by_date['Date'] == target_date]
162
+ if not match.empty:
163
+ payments_within_the_month = match['Monthly Payments'].values[0]
164
+ else:
165
+ payments_within_the_month = 0
166
+
167
+ # Delinquency Risk Metrics
168
+ # total_number_of_dq_accounts = last_month_df[last_month_df['Loan Bucket'].isin([2,3,4,5,6,7,8])].shape[0]
169
+ total_number_of_dq_accounts = last_month_df[last_month_df['Delinquency Status'] == 'Delinquent'].shape[0]
170
+ average_risk_score = round(last_month_df["Risk Score"].mean(), 2)
171
+
172
+ risk_counts = last_month_df["Risk Category"].value_counts().to_dict()
173
+
174
+ ## High risk metrics
175
+ high_risk_count = risk_counts.get("High Risk", 0)
176
+ high_risk_percentage = (high_risk_count / total_number_of_accounts) * 100 if total_number_of_accounts > 0 else 0
177
+ high_risk_percentage = round(high_risk_percentage, 1)
178
+ high_risk_average_score = round(last_month_df[last_month_df["Risk Category"] == "High Risk"]["Risk Score"].mean(), 2) if high_risk_count > 0 else 0
179
+ high_risk_average_loan_amount = round(last_month_df[last_month_df["Risk Category"] == "High Risk"]["Initial Loan Amount"].mean(), 2) if high_risk_count > 0 else 0
180
+
181
+ ## Medium risk metrics
182
+ medium_risk_count = risk_counts.get("Medium Risk", 0)
183
+ medium_risk_percentage = (medium_risk_count / total_number_of_accounts) * 100 if total_number_of_accounts > 0 else 0
184
+ medium_risk_percentage = round(medium_risk_percentage, 1)
185
+ medium_risk_average_score = round(last_month_df[last_month_df["Risk Category"] == "Medium Risk"]["Risk Score"].mean(), 2) if medium_risk_count > 0 else 0
186
+ medium_risk_average_loan_amount = round(last_month_df[last_month_df["Risk Category"] == "Medium Risk"]["Initial Loan Amount"].mean(), 2) if medium_risk_count > 0 else 0
187
+
188
+ ## Low risk metrics
189
+ low_risk_count = risk_counts.get("Low Risk", 0)
190
+ low_risk_percentage = (low_risk_count / total_number_of_accounts) * 100 if total_number_of_accounts > 0 else 0
191
+ low_risk_percentage = round(low_risk_percentage, 1)
192
+ low_risk_average_score = round(last_month_df[last_month_df["Risk Category"] == "Low Risk"]["Risk Score"].mean(), 2) if low_risk_count > 0 else 0
193
+ low_risk_average_loan_amount = round(last_month_df[last_month_df["Risk Category"] == "Low Risk"]["Initial Loan Amount"].mean(), 2) if low_risk_count > 0 else 0
194
+
195
+ ## Pie chart for percentages of accounts in each loan bucket
196
+ loan_bucket_counts = last_month_df['Delinquency Bucket'].value_counts().sort_index()
197
+
198
+ ## Pie chart for proportion percentage of outstanding balance by delinquency bucket
199
+ outstanding_balance_by_bucket = last_month_df.groupby('Delinquency Bucket')['Outstanding Balance'].sum().sort_index()
200
+
201
+ ## Pie chart for account distribution by payment behaviour
202
+ payment_behaviour_counts = last_month_df['Payment Behaviour'].value_counts().sort_index()
203
+
204
+ ## Pie chart for account distribution by collection strategy
205
+ collection_strategy_counts = last_month_df['Recommended Risk Action'].value_counts().sort_index()
206
+
207
+ ## Risk Score Distribution by Deciles
208
+ last_month_df["Risk Decile"] = pd.qcut(last_month_df['Risk Score'], q=10, labels=[f'D{i+1}' for i in range(10)])
209
+ grouped_deciles = last_month_df.groupby(['Risk Decile', 'Delinquency Status']).size().reset_index(name='Count')
210
+ pivot_df_score_deciles = grouped_deciles.pivot(index='Risk Decile', columns='Delinquency Status', values='Count').fillna(0)
211
+ pivot_df_score_deciles = pivot_df_score_deciles.reindex([f'D{i+1}' for i in range(10)]) # Ensure consistent decile order
212
+
213
+ # -----------------------------
214
+ # KPI Summary Cards
215
+ # -----------------------------
216
+ st.header("Portfolio Composition- as of :violet[August 2025]")
217
+
218
+ kpi1, kpi2, kpi3, kpi4 = st.columns(4)
219
+ kpi1.metric(label="Total Number of Accounts", value=total_number_of_accounts, border=True)
220
+ kpi2.metric(label="Total Outstanding Balance", value=f"USD {total_ending_balance:,.2f}", border=True)
221
+ kpi3.metric(label="Average Loan Size", value=f"USD {average_loan_size:,.2f}", border=True)
222
+ kpi4.metric(label="Payments", value=f"USD {payments_within_the_month:,.2f}", border=True)
223
+
224
+ kpi5, kpi6, kpi7 = st.columns(3)
225
+ kpi5.metric(label="Average Loan Amount (High Risk Accounts)", value=f"USD {high_risk_average_loan_amount:,.2f}", border=True)
226
+ kpi6.metric(label="Average Loan Amount (Medium Risk Accounts)", value=f"USD {medium_risk_average_loan_amount:,.2f}", border=True)
227
+ kpi7.metric(label="Average Loan Amount (Low Risk Accounts)", value=f"USD {low_risk_average_loan_amount:,.2f}", border=True)
228
+
229
+ col_pred, col_mid, col_lim = st.columns([3, 1, 3])
230
+
231
+ # Displaying the metrics for the prediction month
232
+ with col_pred:
233
+ st.header("Forecasted Metrics for :violet[February 2026]")
234
+
235
+ st.metric(label="Total Number of Delinquent Accounts", value=total_number_of_dq_accounts, border=True)
236
+ st.metric(label="Average Risk Score (Portfolio Level)", value=558.57, border=True)
237
+
238
+ st.metric(label="High Risk Accounts (%)", value=f"{high_risk_percentage}%", border=True)
239
+ st.metric(label="Average Risk Score (High Risk Accounts)", value=high_risk_average_score, border=True)
240
+
241
+ st.metric(label="Medium Risk Accounts (%)", value=f"{medium_risk_percentage}%", border=True)
242
+ st.metric(label="Average Risk Score (Medium Risk Accounts)", value=medium_risk_average_score, border=True)
243
+
244
+ st.metric(label="Low Risk Accounts (%)", value=f"{low_risk_percentage}%", border=True)
245
+ st.metric(label="Average Risk Score (Low Risk Accounts)", value=low_risk_average_score, border=True)
246
+
247
+ # -----------------------------
248
+ # Account Distribution by Delinquency Bucket
249
+ # -----------------------------
250
+ green_shades = {
251
+ '0-30': '#92ff7b',
252
+ '30+ DPD': '#92ff7b'
253
+ }
254
+
255
+ red_shade_base = '#fe6262'
256
+
257
+ # Define colors based on label
258
+ colors = []
259
+ for label in loan_bucket_counts.index.astype(str):
260
+ if label in green_shades:
261
+ colors.append(green_shades[label])
262
+ else:
263
+ colors.append(red_shade_base)
264
+
265
+ bucket_wise_account_percentages = go.Figure(
266
+ data=[
267
+ go.Pie(
268
+ labels=loan_bucket_counts.index.astype(str),
269
+ values=loan_bucket_counts.values,
270
+ textinfo='label+percent',
271
+ textposition='inside',
272
+ marker=dict(colors=colors, line=dict(color='white', width=2)),
273
+ hovertemplate='<b>Delinquency Bucket:</b> %{label}<br><b>Customers:</b> %{value}<br><b>Share:</b> %{percent}',
274
+ sort=False
275
+ )
276
+ ]
277
+ )
278
+
279
+ st.subheader("Accounts Distribution by Delinquency Bucket")
280
+ bucket_wise_account_percentages.update_layout(
281
+ template='plotly_white',
282
+ margin=dict(t=80, b=50, l=50, r=50)
283
+ )
284
+ st.plotly_chart(bucket_wise_account_percentages, use_container_width=True)
285
+
286
+ # -----------------------------
287
+ # Accounts Distribution by Collection Strategy
288
+ # -----------------------------
289
+ collection_strategy_colors = {
290
+ 'No Action Required': '#92ff7b',
291
+ 'Monitor': '#92ff7b',
292
+ 'Payment reminder email': '#ff9966',
293
+ 'Payment reminder call': '#ff9966',
294
+ 'Debt Relief Plan': '#fe6262',
295
+ 'Downgrade Account': '#fe6262'
296
+ }
297
+
298
+ colors = []
299
+ for label in collection_strategy_counts.index.astype(str):
300
+ colors.append(collection_strategy_colors.get(label, '#d3d3d3')) # default grey if not mapped
301
+
302
+ collection_strategy_percentages = go.Figure(
303
+ data=[
304
+ go.Pie(
305
+ labels=collection_strategy_counts.index.astype(str),
306
+ values=collection_strategy_counts.values,
307
+ textinfo='label+percent',
308
+ textposition='inside',
309
+ marker=dict(colors=colors, line=dict(color="white", width=2)),
310
+ hovertemplate='<b>Collection Strategy:</b> %{label}<br><b>Share:</b> %{percent}',
311
+ sort=False
312
+ )
313
+ ]
314
+ )
315
+
316
+ st.subheader("Accounts Distribution by Collection Strategy")
317
+ collection_strategy_percentages.update_layout(
318
+ template='plotly_white',
319
+ margin=dict(t=80, b=50, l=10, r=100)
320
+ )
321
+
322
+ st.plotly_chart(collection_strategy_percentages, use_container_width=True)
323
+
324
+ with col_mid:
325
+ st.markdown(
326
+ """
327
+ <div style="display: flex; justify-content: center;">
328
+ <div style="height: 225vh; border-left: 2px solid white;"></div>
329
+ </div>
330
+ """,
331
+ unsafe_allow_html=True
332
+ )
333
+
334
+ # Displaying the metrics for the last input month
335
+ with col_lim:
336
+ st.header("Metrics as of :violet[August 2025]")
337
+
338
+ st.metric(label="Total Number of Delinquent Accounts", value=34, border=True)
339
+ st.metric(label="Average Risk Score (Portfolio Level)", value=523.13, border=True)
340
+
341
+ st.metric(label="High Risk Accounts (%)", value=f"{34.0}%", border=True)
342
+ st.metric(label="Average Risk Score (High Risk Accounts)", value=146.90, border=True)
343
+
344
+ st.metric(label="Medium Risk Accounts (%)", value=f"{26.0}%", border=True)
345
+ st.metric(label="Average Risk Score (Medium Risk Accounts)", value=493.14, border=True)
346
+
347
+ st.metric(label="Low Risk Accounts (%)", value=f"{40.0}%", border=True)
348
+ st.metric(label="Average Risk Score (Low Risk Accounts)", value=843.54, border=True)
349
+
350
+ # -----------------------------
351
+ # Account Distribution by Proportion of Outstanding Balance
352
+ # -----------------------------
353
+ green_shades = {
354
+ '0-30': '#92ff7b',
355
+ '30+ DPD': '#92ff7b'
356
+ }
357
+
358
+ red_shade_base = '#fe6262'
359
+
360
+ # Define colors based on label
361
+ colors = []
362
+ for label in outstanding_balance_by_bucket.index.astype(str):
363
+ if label in green_shades:
364
+ colors.append(green_shades[label])
365
+ else:
366
+ colors.append(red_shade_base)
367
+
368
+ bucket_wise_outstanding_balance_percentages = go.Figure(
369
+ data=[
370
+ go.Pie(
371
+ labels=outstanding_balance_by_bucket.index.astype(str),
372
+ values=outstanding_balance_by_bucket.values,
373
+ textinfo='label+percent',
374
+ textposition='inside',
375
+ marker=dict(colors=colors, line=dict(color='white', width=2)),
376
+ hovertemplate='<b>Delinquency Bucket:</b> %{label}<br><b>Outstanding Balance:</b> %{value}<br><b>Share:</b> %{percent}',
377
+ sort=False
378
+ )
379
+ ]
380
+ )
381
+
382
+ st.subheader("Accounts Distribution by Proportion of Ending Balance")
383
+ bucket_wise_outstanding_balance_percentages.update_layout(
384
+ template='plotly_white',
385
+ margin=dict(t=80, b=50, l=50, r=50)
386
+ )
387
+
388
+ st.plotly_chart(bucket_wise_outstanding_balance_percentages, use_container_width=True)
389
+
390
+ # -----------------------------
391
+ # Account Distribution by Payment Behaviour
392
+ # -----------------------------
393
+ payment_behaviour_colors = {
394
+ 'On Time': '#92ff7b',
395
+ 'Late Payer': '#ff9966',
396
+ 'Irregular Payer': '#ff9966',
397
+ 'Non Payer': '#fe6262'
398
+ }
399
+
400
+ # Assign colors based on the label
401
+ colors = []
402
+ for label in payment_behaviour_counts.index.astype(str):
403
+ colors.append(payment_behaviour_colors.get(label, '#d3d3d3')) # default grey if not mapped
404
+
405
+ bucket_wise_payment_behaviour_percentages = go.Figure(
406
+ data=[
407
+ go.Pie(
408
+ labels=payment_behaviour_counts.index.astype(str),
409
+ values=payment_behaviour_counts.values,
410
+ textinfo='label+percent',
411
+ textposition='inside',
412
+ marker=dict(colors=colors, line=dict(color="white", width=2)),
413
+ hovertemplate='<b>Payment Behaviour:</b> %{label}<br><b>Share:</b> %{percent}',
414
+ sort=False
415
+ )
416
+ ]
417
+ )
418
+
419
+ st.markdown("### Accounts Distribution by Payment Behaviour")
420
+ bucket_wise_payment_behaviour_percentages.update_layout(
421
+ template='plotly_white',
422
+ margin=dict(t=80, b=50, l=50, r=50)
423
+ )
424
+
425
+ st.plotly_chart(bucket_wise_payment_behaviour_percentages, use_container_width=True)
426
+
427
+ # -----------------------------
428
+ # Account Distribution by Risk Score vs Loan Status (Decile Wise Snapshot)- as of :violet[February 2026]
429
+ # -----------------------------
430
+ colors = {
431
+ 'Current': '#92ff7b',
432
+ 'Delinquent': '#fe6262'
433
+ }
434
+
435
+ decile_score_distribution = go.Figure()
436
+
437
+ for status in ['Current', 'Delinquent']:
438
+ decile_score_distribution.add_trace(
439
+ go.Bar(
440
+ x=pivot_df_score_deciles.index,
441
+ y=pivot_df_score_deciles[status],
442
+ name=status,
443
+ marker_color=colors[status],
444
+ width=0.4,
445
+ )
446
+ )
447
+
448
+ decile_score_distribution.update_layout(
449
+ barmode='group',
450
+ title={
451
+ 'text': 'Account Distribution by Risk Score vs Delinquency Status (Decile Wise Snapshot)- Forecasted for February 2026',
452
+ 'x': 0.5,
453
+ 'xanchor': 'center',
454
+ 'font': dict(size=29, family=', sans-serif')
455
+ },
456
+ xaxis_title='Risk Score Decile',
457
+ yaxis_title='Number of Customers',
458
+ template='plotly_white',
459
+ legend=dict(
460
+ # title='Loan Status',
461
+ orientation='h',
462
+ yanchor='bottom',
463
+ y=-0.3,
464
+ xanchor='center',
465
+ x=0.5,
466
+ font=dict(size=12)
467
+ ),
468
+ margin=dict(t=80, b=100, l=50, r=50)
469
+ )
470
+ st.plotly_chart(decile_score_distribution, use_container_width=True)
README.md CHANGED
@@ -10,11 +10,4 @@ tags:
10
  pinned: false
11
  short_description: Early Detection Dashboard for Credit Risk
12
  license: apache-2.0
13
- ---
14
-
15
- # Welcome to Streamlit!
16
-
17
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
18
-
19
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
20
- forums](https://discuss.streamlit.io).
 
10
  pinned: false
11
  short_description: Early Detection Dashboard for Credit Risk
12
  license: apache-2.0
13
+ ---
 
 
 
 
 
 
 
SHAP_plots/SHAP_Plot_CUST0001.jpg ADDED

Git LFS Details

  • SHA256: 578219ab64670458d977d70a99113faa23449eb5acef407cd3672af3b8004640
  • Pointer size: 130 Bytes
  • Size of remote file: 31.2 kB
SHAP_plots/SHAP_Plot_CUST0002.jpg ADDED

Git LFS Details

  • SHA256: 025a712ee3c5a7ff96a73e7927105da486b4d4ac48cc5485e365bfe18b4b63a1
  • Pointer size: 130 Bytes
  • Size of remote file: 32.5 kB
SHAP_plots/SHAP_Plot_CUST0003.jpg ADDED

Git LFS Details

  • SHA256: c5d80c2bf9f05f0ae1575ee91afd90a6cf09b2d630ea2813c82eb841f44e02e0
  • Pointer size: 130 Bytes
  • Size of remote file: 30.7 kB
SHAP_plots/SHAP_Plot_CUST0004.jpg ADDED

Git LFS Details

  • SHA256: dcd47db4ea143810f15faa19cc3f31dcdda99c90dc57ab4d352e3a9814e75027
  • Pointer size: 130 Bytes
  • Size of remote file: 33 kB
SHAP_plots/SHAP_Plot_CUST0005.jpg ADDED

Git LFS Details

  • SHA256: 19134aff39ef8d4f529a0d9ceaa96f55836f193dc7c65ec5c4b9ec9412c1fb90
  • Pointer size: 130 Bytes
  • Size of remote file: 31.8 kB
SHAP_plots/SHAP_Plot_CUST0006.jpg ADDED

Git LFS Details

  • SHA256: 9eb4fd0a517bd871e1b288830128637501d43ec08b023410a01df6f2794085c0
  • Pointer size: 130 Bytes
  • Size of remote file: 32.4 kB
SHAP_plots/SHAP_Plot_CUST0007.jpg ADDED

Git LFS Details

  • SHA256: 11287157c7006d6b98ac31c0e6f8993939586fd167708fb4ff691b4c613b5a9b
  • Pointer size: 130 Bytes
  • Size of remote file: 33.2 kB
SHAP_plots/SHAP_Plot_CUST0008.jpg ADDED

Git LFS Details

  • SHA256: aa0092d6c51202ab3712b1eb60b706952df358b885ec49485921f0006d36fe8a
  • Pointer size: 130 Bytes
  • Size of remote file: 32.3 kB
SHAP_plots/SHAP_Plot_CUST0009.jpg ADDED

Git LFS Details

  • SHA256: dbcbcc7851fea209ca6e788e93c9b0098b5b237eb34bd463fe4e0b1d7915e949
  • Pointer size: 130 Bytes
  • Size of remote file: 32.1 kB
SHAP_plots/SHAP_Plot_CUST0010.jpg ADDED

Git LFS Details

  • SHA256: 1a7f21a022df9462406c0176978161b8574697f5b9d27b254508ca6b2d1db1df
  • Pointer size: 130 Bytes
  • Size of remote file: 31.4 kB
SHAP_plots/SHAP_Plot_CUST0011.jpg ADDED

Git LFS Details

  • SHA256: d9524e02b79d36681733e6d9adfb6d391ec419a961867853f0ebc9cbab521d45
  • Pointer size: 130 Bytes
  • Size of remote file: 31.7 kB
SHAP_plots/SHAP_Plot_CUST0012.jpg ADDED

Git LFS Details

  • SHA256: a643bf8675892a11c0e230d5f2cb05f1aafc4c6c6a0a0c2c3f83423ab8fe57e0
  • Pointer size: 130 Bytes
  • Size of remote file: 32.5 kB
SHAP_plots/SHAP_Plot_CUST0013.jpg ADDED

Git LFS Details

  • SHA256: f25391fc3bf107ae14e9c7ef83d694b13680a22374d9d5f1779f43a1cef05404
  • Pointer size: 130 Bytes
  • Size of remote file: 31.3 kB
SHAP_plots/SHAP_Plot_CUST0014.jpg ADDED

Git LFS Details

  • SHA256: c91e48f5ead1fd5e7fd7393f86f1d28818615e19750b61ad00945c6bf931fa83
  • Pointer size: 130 Bytes
  • Size of remote file: 30.8 kB
SHAP_plots/SHAP_Plot_CUST0015.jpg ADDED

Git LFS Details

  • SHA256: ba42d913bd89fa2054007f7a5c04b2db957e2b85293b1bd146d06ac918601bd3
  • Pointer size: 130 Bytes
  • Size of remote file: 33.4 kB
SHAP_plots/SHAP_Plot_CUST0016.jpg ADDED

Git LFS Details

  • SHA256: f4fa0964937379769260208619385dec2ccadd1c36fdedb7b2f01057613c876d
  • Pointer size: 130 Bytes
  • Size of remote file: 33 kB
SHAP_plots/SHAP_Plot_CUST0017.jpg ADDED

Git LFS Details

  • SHA256: 074a1e8cc494b7016019144d9da24eb6fd1debf0d5ce3e571f078f00f8b53432
  • Pointer size: 130 Bytes
  • Size of remote file: 32.1 kB
SHAP_plots/SHAP_Plot_CUST0018.jpg ADDED

Git LFS Details

  • SHA256: 0a34f797aed655f4b52f1e9a8c240f713c34adeb32b32db4182a15b8f8da5513
  • Pointer size: 130 Bytes
  • Size of remote file: 33.7 kB
SHAP_plots/SHAP_Plot_CUST0019.jpg ADDED

Git LFS Details

  • SHA256: 9eb24dac92ce9dfd1d5465b391581cdee0fc47236024ea6a567d48f236062f7d
  • Pointer size: 130 Bytes
  • Size of remote file: 31.3 kB
SHAP_plots/SHAP_Plot_CUST0020.jpg ADDED

Git LFS Details

  • SHA256: c425e60e88662e2a7f220ece2e8e4f5feab595fac0d0baeac2a5618792793824
  • Pointer size: 130 Bytes
  • Size of remote file: 32 kB
SHAP_plots/SHAP_Plot_CUST0021.jpg ADDED

Git LFS Details

  • SHA256: 02682556ac3f4bd38b6cd19c1ec4243f86b610982a303b3d5b231e8c1bbe398e
  • Pointer size: 130 Bytes
  • Size of remote file: 33.5 kB
SHAP_plots/SHAP_Plot_CUST0022.jpg ADDED

Git LFS Details

  • SHA256: e8fef6dc9e3fcfe912f9f3e24c06c7cb2acc3db9ef53a2aeaad189b6cbb6eeab
  • Pointer size: 130 Bytes
  • Size of remote file: 32.6 kB
SHAP_plots/SHAP_Plot_CUST0023.jpg ADDED

Git LFS Details

  • SHA256: 8c0bd95c3ac02407836f9adee35f7c64feb77dcc8c41d6d07f88730f92c55095
  • Pointer size: 130 Bytes
  • Size of remote file: 33.3 kB
SHAP_plots/SHAP_Plot_CUST0024.jpg ADDED

Git LFS Details

  • SHA256: dc102f690a04234312102a400007305d2316674a5b8f7db6e92bad74c0050486
  • Pointer size: 130 Bytes
  • Size of remote file: 31.8 kB
SHAP_plots/SHAP_Plot_CUST0025.jpg ADDED

Git LFS Details

  • SHA256: 1776f7ac879669990770bf96a639e693c47fd6d1f1d9fcbf5d830b3e2ed13933
  • Pointer size: 130 Bytes
  • Size of remote file: 33 kB
SHAP_plots/SHAP_Plot_CUST0026.jpg ADDED

Git LFS Details

  • SHA256: 141cf91a8971caebaf16286d954633d61568281f6d8bd500a634f233ce389f6c
  • Pointer size: 130 Bytes
  • Size of remote file: 33.2 kB
SHAP_plots/SHAP_Plot_CUST0027.jpg ADDED

Git LFS Details

  • SHA256: a7c126e1253d42dfb85c75b49a0087d2d433ab436e24e33f494be8ec618eeb24
  • Pointer size: 130 Bytes
  • Size of remote file: 33.1 kB
SHAP_plots/SHAP_Plot_CUST0028.jpg ADDED

Git LFS Details

  • SHA256: e0116e34913c8f146de1fa0f078bc350f0fd08cda9c1a8d8e689d3701ffca01b
  • Pointer size: 130 Bytes
  • Size of remote file: 32.2 kB
SHAP_plots/SHAP_Plot_CUST0029.jpg ADDED

Git LFS Details

  • SHA256: 31af4ced0dc1b3b1c8b6f9589df50f4a08125407ffac8ba20c25305cee9aa757
  • Pointer size: 130 Bytes
  • Size of remote file: 33.1 kB
SHAP_plots/SHAP_Plot_CUST0030.jpg ADDED

Git LFS Details

  • SHA256: 5e9632e19e3372caf80ab2b24839f765ee7c4338cf7b1ca89891e77a56f64ca7
  • Pointer size: 130 Bytes
  • Size of remote file: 32.2 kB
SHAP_plots/SHAP_Plot_CUST0031.jpg ADDED

Git LFS Details

  • SHA256: b5c6240be5c2e5af2be6a07fe4765f07f8d81e7efb9a3352fe5be81a1695cd9b
  • Pointer size: 130 Bytes
  • Size of remote file: 31.9 kB
SHAP_plots/SHAP_Plot_CUST0032.jpg ADDED

Git LFS Details

  • SHA256: 6f9449898c6e27e6978fd96273fa4776ebfcc0b1d64cad4d5c8d47b5e60aef01
  • Pointer size: 130 Bytes
  • Size of remote file: 31.9 kB
SHAP_plots/SHAP_Plot_CUST0033.jpg ADDED

Git LFS Details

  • SHA256: 2ffa82443080d663c730df99b86fdaea26ab36c2b57efe1c40d5bc372b85a224
  • Pointer size: 130 Bytes
  • Size of remote file: 33.8 kB
SHAP_plots/SHAP_Plot_CUST0034.jpg ADDED

Git LFS Details

  • SHA256: abf8b7587c141c831fdf340f144828988d0de9f452f5cc78076f9c8cba54debc
  • Pointer size: 130 Bytes
  • Size of remote file: 31.2 kB
SHAP_plots/SHAP_Plot_CUST0035.jpg ADDED

Git LFS Details

  • SHA256: 032e06638a53f798ea0bd4f7d23504447a953b6cfec18fd161b4e4d75881c6ed
  • Pointer size: 130 Bytes
  • Size of remote file: 32.5 kB
SHAP_plots/SHAP_Plot_CUST0036.jpg ADDED

Git LFS Details

  • SHA256: 9f027f2e503f719a2816aa79787fa494aa94e8dfce992dea8fb37258c20b88e0
  • Pointer size: 130 Bytes
  • Size of remote file: 33.2 kB
SHAP_plots/SHAP_Plot_CUST0037.jpg ADDED

Git LFS Details

  • SHA256: 562d3723663dda1dbdee1a6ae4d7ba17e75b5977a1de4855f691392b39e483bf
  • Pointer size: 130 Bytes
  • Size of remote file: 33.6 kB
SHAP_plots/SHAP_Plot_CUST0038.jpg ADDED

Git LFS Details

  • SHA256: fdf812e8aab19adf892ee396c7abfdb80ec1935069f6813cbfbaee8a8dcd693c
  • Pointer size: 130 Bytes
  • Size of remote file: 31.2 kB
SHAP_plots/SHAP_Plot_CUST0039.jpg ADDED

Git LFS Details

  • SHA256: 446b2ea814005be1365d5738be940b1b4f545822324310e64521da5b0677fc45
  • Pointer size: 130 Bytes
  • Size of remote file: 31.8 kB
SHAP_plots/SHAP_Plot_CUST0040.jpg ADDED

Git LFS Details

  • SHA256: 585065fd0af8f30c51f0b8bfb78148c8e2b7f2e7e550cdf0b8d42e167d37739e
  • Pointer size: 130 Bytes
  • Size of remote file: 32.6 kB
SHAP_plots/SHAP_Plot_CUST0041.jpg ADDED

Git LFS Details

  • SHA256: b1f15000a9d4b3e1c009bef975cdc02be304f855612c5f9f195978f91f59a4fe
  • Pointer size: 130 Bytes
  • Size of remote file: 30.8 kB
SHAP_plots/SHAP_Plot_CUST0042.jpg ADDED

Git LFS Details

  • SHA256: d0fa2cb24a76389ea0f8dd8c4976a25bab56ec2b7d82287ac6dcb68fb274fa94
  • Pointer size: 130 Bytes
  • Size of remote file: 32.2 kB
SHAP_plots/SHAP_Plot_CUST0043.jpg ADDED

Git LFS Details

  • SHA256: 6e80de009323f370bd6f3bfa69c753560533eeeb1d8b0a3534c9e3dbdc3c829a
  • Pointer size: 130 Bytes
  • Size of remote file: 31.1 kB
SHAP_plots/SHAP_Plot_CUST0044.jpg ADDED

Git LFS Details

  • SHA256: 7f4a9a63e15c346752124f530321634e8534107ae9ab4f405f5a7e1333f922ee
  • Pointer size: 130 Bytes
  • Size of remote file: 33.8 kB
SHAP_plots/SHAP_Plot_CUST0045.jpg ADDED

Git LFS Details

  • SHA256: 5a5cfb2138394d123a327cfd7cce8e861ef39501cf66b44cccb43fa0015a7e9f
  • Pointer size: 130 Bytes
  • Size of remote file: 33 kB
SHAP_plots/SHAP_Plot_CUST0046.jpg ADDED

Git LFS Details

  • SHA256: f89b023d557a079506994528c5edfc3a35d9a6848e8e8f68b181a988be3920b1
  • Pointer size: 130 Bytes
  • Size of remote file: 32.3 kB