import pandas as pd import streamlit as st from modules.data_loader import load_data from modules.stats_engine import get_baseline_default_rate, calculate_woe_iv import plotly.express as px import plotly.graph_objects as go #Page Configuration st.set_page_config(page_title="Credit Risk Scorecard",initial_sidebar_state="expanded",layout="wide") #data ingestio and state initialization if 'engine_initialized' not in st.session_state: with st.spinner("Initializing Risk Engine........."): st.session_state.df = load_data() st.session_state.baseline_rate = get_baseline_default_rate(st.session_state.df) st.session_state.engine_initialized = True df = st.session_state.df baseline_rate = st.session_state.baseline_rate #sidebar setup with st.sidebar: st.title("Model Controls") st.markdown("Adjust these parameters below to test the risk engine") st.info("System Status: Online") st.caption(f"Rows Loaded in Memory: {len(df):,}") with st.expander("Category Slicers", expanded=False,width=325): with st.form("tab1_slicers"): s_col1, s_col2 = st.columns(2) grade_options = sorted(df['grade'].dropna().unique().tolist()) if 'grade' in df.columns else ['A', 'B', 'C', 'D'] selected_grades = st.multiselect("Risk Grades", options=grade_options, default=grade_options) home_options = ["All"] + df['home_ownership'].dropna().unique().tolist() if 'home_ownership' in df.columns else ["All", "RENT", "OWN", "MORTGAGE"] selected_home = st.selectbox("Home Ownership", options=home_options) apply_filters = st.form_submit_button("Apply Filters", type="primary") if apply_filters: mask = pd.Series(True, index=df.index) if selected_grades: mask &= (df['grade'].isin(selected_grades)) if selected_home != "All": mask &= (df['home_ownership'] == selected_home) display_df = df[mask] else: display_df = df #main dashboard layout or architecture st.title("Credit Risk Scoring and Analysis Engine",text_alignment='center') if "active_tab" not in st.session_state: st.session_state["active_tab"] = "Executive Overview" tab1,tab2,tab3 = st.tabs(["Executive Overview","Statistical Analysis","Live Risk Calculator"],on_change='rerun',key='active_tab') if st.session_state["active_tab"] == "Executive Overview": # to prevent the remaining tabs from loading until the first tab is selected with tab1: st.header("Executive Risk Overview") col1,col2,col3 = st.columns(3) st.markdown( """ """, unsafe_allow_html=True ) with col1: st.metric("Total Applications",value =f"{len(display_df):,}") with col2: total_exposure = display_df['loan_amnt'].sum() if 'loan_amnt' in display_df.columns else 0 st.metric("Total Exposure $",value=f"{total_exposure:,}") with col3: bad_loans = display_df['bad_loan'].sum() if 'bad_loan' in display_df.columns else 0 st.metric("Total Bad Loans",value = f"{bad_loans:,}") gauge_fig = go.Figure(go.Indicator( mode = "gauge+number", value = baseline_rate * 100, number = {'suffix': "%", 'valueformat': ".2f"}, title = {'text': "System Baseline Risk"}, gauge = { 'axis': {'range': [None, 30]}, 'bar': {'color': "darkblue"}, 'steps': [ {'range': [0, 10], 'color': "lightgreen"}, {'range': [10, 15], 'color': "gold"}, {'range': [15, 30], 'color': "crimson"} ], } )) gauge_fig.update_layout(height=200, margin=dict(l=10, r=10, t=50, b=5), paper_bgcolor='rgba(0,0,0,0)', font={'color': "black"}) st.plotly_chart(gauge_fig, use_container_width=True) st.subheader("Portfolio Distribution: Good V/s Bad Loans") status_counts = display_df['bad_loan'].value_counts().reset_index() status_counts.columns = ['Loan_Status', 'Count'] status_counts['Loan_Status'] = status_counts['Loan_Status'].map({0: 'Good Loans', 1: 'Bad Loans'}) fig = px.bar(status_counts,x='Loan_Status',y='Count',color="Loan_Status", color_discrete_map={'Good Loans': '#1E3A8A', 'Bad Loans': '#E11D48'},text_auto=True) fig.update_traces(marker_line_width=1.5,opacity=0.9, hovertemplate="%{x}
Count: %{y:,}") fig.update_layout(plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',showlegend=False, transition_duration=500,hovermode="x unified") st.plotly_chart(fig,use_container_width=True) st.subheader("Time Series Trend: Default Rate Over Time") with st.spinner("Calculating Temporal Trend"): trend_df = display_df.groupby("issue_d").agg(Default_Rate = ("bad_loan","mean")).reset_index() trend_df.sort_values("issue_d", inplace=True) fig_trend = px.line(trend_df,x='issue_d',y='Default_Rate',markers=True, line_shape='spline') fig_trend.update_traces(line_color='#1E3A8A', line_width=3, marker=dict(size=8, color='#E11D48') ) fig_trend.update_layout(plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', xaxis_title="Time Period",yaxis_title="Default Rate",hovermode="x unified", margin=dict(l=0, r=0, t=30, b=0) ) st.plotly_chart(fig_trend,use_container_width=True) elif st.session_state["active_tab"] == "Statistical Analysis": with tab2: st.header("Statiscal Intelligence (WoE and IV)") exclude_cols =['bad_loan','loan_amnt','issue_d','year','loan_status'] feature_options = [col for col in display_df.select_dtypes(include=['object', 'category', 'string']).columns if col not in exclude_cols] if not feature_options: feature_options = ['grade', 'home_ownership', 'purpose', 'verification_status'] selected_feature = st.selectbox("Select Risk Factor for Information Value Analysis", options=map(lambda x: x.replace("_"," ").title(),feature_options)) with st.spinner(f"Computing Weight of Evidence for {selected_feature}...."): woe_df,iv_value = calculate_woe_iv(display_df,selected_feature.lower().replace(' ','_')) if iv_value<0.02: iv_strength = "Useless Predictor" elif iv_value<0.1: iv_strength = "Weak Predictor" elif iv_value<0.3: iv_strength = "Medium Predictor" elif iv_value<0.5: iv_strength = "Strong Predictor" else: iv_strength = "Suspiciously Strong Predictor/Too Good to be True" stat_col1,stat_col2 = st.columns([1,3]) with stat_col1: st.metric("Information Value", f"{iv_value:.4f}") st.caption("Higher IV values signify stronger capability to segregate good loans from bad loans.") st.markdown(f"Predictive Strength: {iv_strength}") with stat_col2: st.subheader(f"Weight of Evidence (WoE) Trend: {selected_feature.replace('_',' ').title()}") fig2 = px.bar(woe_df,x=selected_feature.lower().replace(' ','_'),y='WoE',color='WoE',text_auto='.2f',color_continuous_scale=px.colors.diverging.RdYlBu) fig2.update_layout(plot_bgcolor='rgba(0,0,0,0)',paper_bgcolor='rgba(0,0,0,0)',xaxis_title=f"Categories of {selected_feature.title()}", yaxis_title="WoE Score" ) st.plotly_chart(fig2, use_container_width=True) elif st.session_state["active_tab"] == "Live Risk Calculator": with tab3: st.header("Live Interactive Risk Calculator") st.markdown("Enter applicant details to simulate a real-time credit decision. Calculation triggers only on demand.") available_grades = sorted(df['grade'].dropna().unique()) if 'grade' in df.columns else ['A', 'B', 'C', 'D'] available_home = df['home_ownership'].dropna().unique() if 'home_ownership' in df.columns else ['RENT', 'OWN', 'MORTGAGE'] with st.form("risk_engine_form", clear_on_submit=False): c1, c2 = st.columns(2) with c1: grade_input = st.selectbox("Assigned Grade", options=available_grades) loan_amnt_input = st.number_input("Loan Amount ($)", min_value=1000, max_value=50000, step=500) with c2: home_input = st.selectbox("Home Ownership", options=available_home) int_rate_input = st.slider("Interest Rate (%)", 5.0, 30.0, 10.0) submit_button = st.form_submit_button(label="Analyze Risk Profile 🚀", type="primary") if submit_button: with st.spinner("Processing through Risk Inference Engine..."): applicant_data = { "grade": grade_input, "loan_amnt": loan_amnt_input, "home_ownership": home_input, "int_rate": int_rate_input } risk_score = (loan_amnt_input / 50000) * 0.4 + (int_rate_input / 30) * 0.6 risk_category = "High Risk" if risk_score > 0.5 else "Low Risk" st.markdown("---") res1, res2 = st.columns(2) res1.metric("Calculated Risk Score", f"{risk_score:.2f}") res2.metric("Decision", risk_category, delta_color="inverse") st.success("Analysis Complete.")