brunaaaz commited on
Commit
c48e27c
·
verified ·
1 Parent(s): 8dcfe0f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +113 -113
app.py CHANGED
@@ -18,18 +18,19 @@ import warnings
18
  # Configuração da Página
19
  st.set_page_config(page_title="CrediFast - Risco de Crédito", layout="wide", page_icon="💰")
20
 
21
- # Desativar avisos
 
22
  warnings.filterwarnings('ignore')
23
 
24
  # Título e Cabeçalho
25
  st.title("💰 CrediFast: Sistema Inteligente de Risco de Crédito")
26
  st.markdown("---")
27
 
28
- # --- FUNÇÕES DE CACHE (Para performance) ---
29
 
30
  @st.cache_data
31
  def carregar_dados():
32
- # Carrega diretamente o arquivo local
33
  try:
34
  df = pd.read_csv('credit_risk_dataset.csv')
35
  return df
@@ -46,7 +47,7 @@ def processar_dados(df):
46
  X = df.drop(columns=[target])
47
  y = df[target]
48
 
49
- # 3. Tratamento de Nulos e Encoding
50
  # Numéricas
51
  num_cols = X.select_dtypes(include=['number']).columns.tolist()
52
  imputer_num = SimpleImputer(strategy='median')
@@ -58,61 +59,58 @@ def processar_dados(df):
58
 
59
  return X, y, df # Retorna df original limpo para visualização
60
 
61
- # Renomeado para v2 para forçar o Streamlit a limpar o cache antigo e aplicar o fix do base_score
62
  @st.cache_resource
63
- def treinar_modelo_v2(X, y):
64
  # Split
65
  X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
66
 
67
- # SMOTE
68
  smote = SMOTE(random_state=42)
69
  X_train_bal, y_train_bal = smote.fit_resample(X_train, y_train)
70
 
71
- # Scaling (necessário para Clusters, opcional para XGBoost mas bom para padronizar)
72
  scaler = StandardScaler()
73
  X_train_scaled = scaler.fit_transform(X_train_bal)
74
  X_test_scaled = scaler.transform(X_test)
75
 
76
- # Recuperar nomes das colunas para interpretabilidade
77
  feature_names = X.columns.tolist()
78
  X_train_final = pd.DataFrame(X_train_scaled, columns=feature_names)
79
  X_test_final = pd.DataFrame(X_test_scaled, columns=feature_names)
80
 
81
  # Treinamento XGBoost
82
- # FIX: base_score=0.5 ajuda a evitar o erro '[5E-1]' no SHAP com XGBoost novos
83
- model = XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=42, base_score=0.5)
 
 
 
 
 
84
  model.fit(X_train_final, y_train_bal)
85
 
86
- # Garantia extra: forçar parametro no booster interno
87
- try:
88
- model.get_booster().set_param({'base_score': 0.5})
89
- except:
90
- pass
91
-
92
  return model, scaler, X_test_final, y_test, X_train_final, feature_names
93
 
94
- # --- LOGICA PRINCIPAL ---
95
 
96
- # Tenta carregar os dados automaticamente
97
  df_raw = carregar_dados()
98
 
99
  if df_raw is not None:
100
  # Processamento Automático
101
- # Mostra um spinner enquanto carrega para o usuário saber que está trabalhando
102
  with st.spinner('Inicializando sistema: Processando dados e treinando IA...'):
103
  X, y, df_clean = processar_dados(df_raw)
104
- # Chamando a função v2 para garantir que o fix seja usado
105
- model, scaler, X_test, y_test, X_train, feature_names = treinar_modelo_v2(X, y)
106
 
107
- # --- SIDEBAR (Simulador) ---
108
  st.sidebar.header("📂 Menu")
109
- st.sidebar.success("✅ Modelo Carregado e Pronto")
110
 
111
  st.sidebar.markdown("---")
112
  st.sidebar.subheader("🎲 Simulador de Crédito")
113
  st.sidebar.info("Simule um perfil para ver a probabilidade de calote.")
114
 
115
- # Inputs do Simulador (Principais features)
116
  sim_income = st.sidebar.number_input("Renda Anual", value=50000)
117
  sim_age = st.sidebar.number_input("Idade", value=25)
118
  sim_loan = st.sidebar.number_input("Valor do Empréstimo", value=10000)
@@ -121,16 +119,18 @@ if df_raw is not None:
121
 
122
  # Botão Simular
123
  if st.sidebar.button("Calcular Risco"):
124
- # Lógica simplificada de simulação
125
  input_data = pd.DataFrame(0, index=[0], columns=feature_names)
126
- input_data['person_income'] = sim_income
127
- input_data['person_age'] = sim_age
128
- input_data['loan_amnt'] = sim_loan
129
- input_data['loan_int_rate'] = sim_int_rate
130
- input_data['person_emp_length'] = sim_emp_length
131
- input_data['loan_percent_income'] = sim_loan / sim_income if sim_income > 0 else 0
 
 
132
 
133
- # Escalonar
134
  input_scaled = scaler.transform(input_data)
135
  prob = model.predict_proba(input_scaled)[0][1]
136
 
@@ -139,13 +139,13 @@ if df_raw is not None:
139
  else:
140
  st.sidebar.success(f"🟢 Aprovado: {prob:.1%} de chance de Default")
141
 
142
- # --- TABS DO DASHBOARD ---
143
  tab1, tab2, tab3, tab4, tab5 = st.tabs([
144
  "📊 Diagnóstico",
145
- "🤖 Performance do Modelo",
146
  "🧠 Explicabilidade (SHAP)",
147
- "🧩 Segmentação (Clusters)",
148
- "📝 Relatório Gerencial"
149
  ])
150
 
151
  # TAB 1: Diagnóstico
@@ -158,88 +158,95 @@ if df_raw is not None:
158
  fig_pie = px.pie(names=['Good (0)', 'Bad (1)'],
159
  values=y.value_counts().values,
160
  color_discrete_sequence=['blue', 'red'])
161
- st.plotly_chart(fig_pie, width="stretch")
162
 
163
  with col2:
164
- st.markdown("**Distribuição de Renda vs Empréstimo**")
165
  fig_scatter = px.scatter(df_clean.head(1000), x='person_income', y='loan_amnt',
166
  color=y.head(1000).astype(str),
167
  color_discrete_map={'0': 'blue', '1': 'red'},
168
  title="Amostra de 1000 clientes")
169
- st.plotly_chart(fig_scatter, width="stretch")
170
 
171
- st.warning("Nota: Foi aplicado SMOTE (Balanceamento) nos dados de treino para corrigir a desproporção vista acima.")
172
 
173
  # TAB 2: Performance
174
  with tab2:
175
- st.subheader("Avaliação do Modelo Vencedor (XGBoost)")
176
 
177
  y_pred = model.predict(X_test)
178
  y_proba = model.predict_proba(X_test)[:, 1]
179
 
180
- m1, m2, m3, m4 = st.columns(4)
181
- m1.metric("AUC Score", f"{roc_auc_score(y_test, y_proba):.3f}")
182
- m2.metric("Recall (Segurança)", f"{recall_score(y_test, y_pred):.3f}")
183
- m3.metric("Precisão", f"{precision_score(y_test, y_pred):.3f}")
184
- m4.metric("Acurácia", f"{accuracy_score(y_test, y_pred):.3f}")
185
 
186
  st.markdown("### Matriz de Confusão")
187
  cm = confusion_matrix(y_test, y_pred)
188
  fig_cm = px.imshow(cm, text_auto=True, color_continuous_scale='Blues',
189
  labels=dict(x="Predito", y="Real", color="Qtd"),
190
  x=['Good', 'Bad'], y=['Good', 'Bad'])
191
- st.plotly_chart(fig_cm)
192
 
193
- # TAB 3: SHAP
194
  with tab3:
195
- st.subheader("Por que o modelo toma essas decisões?")
196
 
197
  try:
198
- # Calcular SHAP
199
- # TENTATIVA 1: Explainer Padrão
200
  explainer = shap.TreeExplainer(model)
201
  shap_values = explainer.shap_values(X_test)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  except Exception as e:
203
- # TENTATIVA 2: Fallback para erro de versão XGBoost/SHAP
204
  if "could not convert string to float" in str(e):
205
- st.warning("🔄 Ajustando compatibilidade do SHAP... (Isso é normal em versões novas)")
206
- # Usa o booster interno diretamente, ignorando o wrapper sklearn que causa o erro
207
- explainer = shap.TreeExplainer(model.get_booster())
208
- shap_values = explainer.shap_values(X_test)
 
 
 
 
 
 
 
209
  else:
210
- raise e
211
-
212
- st.markdown("**1. Visão Global (Quais variáveis importam mais?)**")
213
- # Correção para exibir o gráfico sem warning: criar figura explícita e passar para st.pyplot
214
- fig_summary, ax = plt.subplots()
215
- shap.summary_plot(shap_values, X_test, show=False)
216
- st.pyplot(plt.gcf())
217
- plt.clf() # Limpar figura atual
218
-
219
- st.markdown("---")
220
- st.markdown("**2. Visão Local (Análise caso a caso)**")
221
-
222
- # Seletor de índice
223
- idx = st.number_input("Selecione o ID do Cliente para auditar:", min_value=0, max_value=len(X_test)-1, value=0)
224
-
225
- real_val = y_test.iloc[idx]
226
- pred_val = y_pred[idx]
227
- st.write(f"Cliente ID {idx} | Real: {'Bad' if real_val==1 else 'Good'} | Predito: {'Bad' if pred_val==1 else 'Good'}")
228
-
229
- # Waterfall Plot
230
- fig_waterfall = plt.figure()
231
- shap.plots.waterfall(shap.Explanation(values=shap_values[idx],
232
- base_values=explainer.expected_value,
233
- data=X_test.iloc[idx],
234
- feature_names=X_test.columns.tolist()),
235
- max_display=10, show=False)
236
- st.pyplot(fig_waterfall)
237
 
238
  # TAB 4: Clusters
239
  with tab4:
240
- st.subheader("Segmentação de Clientes (KMeans & PCA)")
241
 
242
- with st.spinner("Gerando Clusters..."):
243
  # KMeans
244
  kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
245
  clusters = kmeans.fit_predict(X_test)
@@ -248,7 +255,7 @@ if df_raw is not None:
248
  dbscan = DBSCAN(eps=3.0, min_samples=5)
249
  outliers = dbscan.fit_predict(X_test)
250
 
251
- # PCA
252
  pca = PCA(n_components=2)
253
  components = pca.fit_transform(X_test)
254
 
@@ -258,42 +265,35 @@ if df_raw is not None:
258
  df_viz['Status Real'] = y_test.values
259
  df_viz['Status Real'] = df_viz['Status Real'].map({0: 'Good', 1: 'Bad'})
260
 
261
- # Plot
262
  fig_cluster = px.scatter(df_viz, x='PC1', y='PC2', color='Cluster',
263
  symbol='Status Real',
264
  title="Mapa de Segmentação de Risco",
265
  color_discrete_sequence=px.colors.qualitative.Safe)
266
- st.plotly_chart(fig_cluster, width="stretch")
267
 
268
- st.markdown("#### Detecção de Anomalias (Outliers)")
269
- st.info(f"O algoritmo DBSCAN detectou {sum(outliers == -1)} casos anômalos que requerem revisão manual.")
270
 
271
- # TAB 5: Recomendações
272
  with tab5:
273
- st.subheader("📋 Relatório Gerencial e Estratégia")
274
-
275
  st.markdown("""
276
- ### 1. Diagnóstico do Modelo
277
- O modelo **XGBoost** foi selecionado como o mais robusto. Priorizamos o **Recall** para minimizar perdas financeiras (falsos negativos), mantendo uma precisão operacionalmente viável.
278
-
279
- ### 2. Fatores Críticos (SHAP)
280
- * **Comprometimento de Renda (`loan_percent_income`):** É o maior preditor de risco. Clientes comprometendo >30% da renda disparam alarmes.
281
- * **Histórico (`cb_person_default_on_file`):** Inadimplência prévia é determinante.
282
- * **Taxa de Juros (`loan_int_rate`):** Juros abusivos correlacionam com maior inadimplência (seleção adversa).
283
-
284
- ### 3. Plano de Ação (Recomendações)
285
 
286
- | Ação | Descrição | Impacto Esperado |
287
- | :--- | :--- | :--- |
288
- | **Travas de Segurança** | Bloquear empréstimos onde parcela > 30% da renda sem garantia real. | Redução drástica de Default. |
289
- | **Mesa de Crédito** | Clientes do **Cluster de Risco** ou com histórico negativo vão para análise humana. | Melhoria na qualidade da carteira. |
290
- | **Juros Inteligentes** | Evitar taxas predatórias que forçam o calote; focar em crédito sustentável. | Aumento do LTV (Lifetime Value). |
291
 
292
- ---
293
- *Relatório gerado automaticamente pelo Sistema CrediFast AI.*
 
 
 
 
 
 
 
294
  """)
295
 
296
  else:
297
- # Caso o arquivo não seja encontrado
298
- st.error("🚨 Arquivo `credit_risk_dataset.csv` não encontrado no diretório.")
299
- st.info("Por favor, adicione o arquivo csv na aba 'Files' do Hugging Face Spaces junto com este app.py.")
 
18
  # Configuração da Página
19
  st.set_page_config(page_title="CrediFast - Risco de Crédito", layout="wide", page_icon="💰")
20
 
21
+ # Desativar avisos de depreciação do Pyplot e outros warnings
22
+ st.set_option('deprecation.showPyplotGlobalUse', False)
23
  warnings.filterwarnings('ignore')
24
 
25
  # Título e Cabeçalho
26
  st.title("💰 CrediFast: Sistema Inteligente de Risco de Crédito")
27
  st.markdown("---")
28
 
29
+ # --- FUNÇÕES DE CACHE (Para performance e estabilidade) ---
30
 
31
  @st.cache_data
32
  def carregar_dados():
33
+ # Tenta carregar o arquivo localmente
34
  try:
35
  df = pd.read_csv('credit_risk_dataset.csv')
36
  return df
 
47
  X = df.drop(columns=[target])
48
  y = df[target]
49
 
50
+ # 3. Tratamento de Nulos
51
  # Numéricas
52
  num_cols = X.select_dtypes(include=['number']).columns.tolist()
53
  imputer_num = SimpleImputer(strategy='median')
 
59
 
60
  return X, y, df # Retorna df original limpo para visualização
61
 
62
+ # Usamos v4 no nome para garantir que o cache antigo seja invalidado
63
  @st.cache_resource
64
+ def treinar_modelo_v4(X, y):
65
  # Split
66
  X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
67
 
68
+ # SMOTE (Apenas no treino)
69
  smote = SMOTE(random_state=42)
70
  X_train_bal, y_train_bal = smote.fit_resample(X_train, y_train)
71
 
72
+ # Scaling
73
  scaler = StandardScaler()
74
  X_train_scaled = scaler.fit_transform(X_train_bal)
75
  X_test_scaled = scaler.transform(X_test)
76
 
77
+ # Recuperar nomes das colunas
78
  feature_names = X.columns.tolist()
79
  X_train_final = pd.DataFrame(X_train_scaled, columns=feature_names)
80
  X_test_final = pd.DataFrame(X_test_scaled, columns=feature_names)
81
 
82
  # Treinamento XGBoost
83
+ # Definimos base_score=0.5 explicitamente para ajudar na compatibilidade com SHAP
84
+ model = XGBClassifier(
85
+ use_label_encoder=False,
86
+ eval_metric='logloss',
87
+ random_state=42,
88
+ base_score=0.5
89
+ )
90
  model.fit(X_train_final, y_train_bal)
91
 
 
 
 
 
 
 
92
  return model, scaler, X_test_final, y_test, X_train_final, feature_names
93
 
94
+ # --- LÓGICA PRINCIPAL DO DASHBOARD ---
95
 
96
+ # Tenta carregar os dados
97
  df_raw = carregar_dados()
98
 
99
  if df_raw is not None:
100
  # Processamento Automático
 
101
  with st.spinner('Inicializando sistema: Processando dados e treinando IA...'):
102
  X, y, df_clean = processar_dados(df_raw)
103
+ model, scaler, X_test, y_test, X_train, feature_names = treinar_modelo_v4(X, y)
 
104
 
105
+ # --- BARRA LATERAL (Simulador) ---
106
  st.sidebar.header("📂 Menu")
107
+ st.sidebar.success("✅ Modelo Carregado")
108
 
109
  st.sidebar.markdown("---")
110
  st.sidebar.subheader("🎲 Simulador de Crédito")
111
  st.sidebar.info("Simule um perfil para ver a probabilidade de calote.")
112
 
113
+ # Inputs do Simulador
114
  sim_income = st.sidebar.number_input("Renda Anual", value=50000)
115
  sim_age = st.sidebar.number_input("Idade", value=25)
116
  sim_loan = st.sidebar.number_input("Valor do Empréstimo", value=10000)
 
119
 
120
  # Botão Simular
121
  if st.sidebar.button("Calcular Risco"):
122
+ # Criação do dataframe de input
123
  input_data = pd.DataFrame(0, index=[0], columns=feature_names)
124
+ # Preenchendo valores
125
+ if 'person_income' in input_data.columns: input_data['person_income'] = sim_income
126
+ if 'person_age' in input_data.columns: input_data['person_age'] = sim_age
127
+ if 'loan_amnt' in input_data.columns: input_data['loan_amnt'] = sim_loan
128
+ if 'loan_int_rate' in input_data.columns: input_data['loan_int_rate'] = sim_int_rate
129
+ if 'person_emp_length' in input_data.columns: input_data['person_emp_length'] = sim_emp_length
130
+ if 'loan_percent_income' in input_data.columns:
131
+ input_data['loan_percent_income'] = sim_loan / sim_income if sim_income > 0 else 0
132
 
133
+ # Escalonar e Prever
134
  input_scaled = scaler.transform(input_data)
135
  prob = model.predict_proba(input_scaled)[0][1]
136
 
 
139
  else:
140
  st.sidebar.success(f"🟢 Aprovado: {prob:.1%} de chance de Default")
141
 
142
+ # --- ABAS ---
143
  tab1, tab2, tab3, tab4, tab5 = st.tabs([
144
  "📊 Diagnóstico",
145
+ "🤖 Performance",
146
  "🧠 Explicabilidade (SHAP)",
147
+ "🧩 Segmentação",
148
+ "📝 Relatório"
149
  ])
150
 
151
  # TAB 1: Diagnóstico
 
158
  fig_pie = px.pie(names=['Good (0)', 'Bad (1)'],
159
  values=y.value_counts().values,
160
  color_discrete_sequence=['blue', 'red'])
161
+ st.plotly_chart(fig_pie, use_container_width=True) # Versão antiga compatível
162
 
163
  with col2:
164
+ st.markdown("**Distribuição: Renda vs Empréstimo**")
165
  fig_scatter = px.scatter(df_clean.head(1000), x='person_income', y='loan_amnt',
166
  color=y.head(1000).astype(str),
167
  color_discrete_map={'0': 'blue', '1': 'red'},
168
  title="Amostra de 1000 clientes")
169
+ st.plotly_chart(fig_scatter, use_container_width=True)
170
 
171
+ st.warning("Nota: Foi aplicado SMOTE (Balanceamento) nos dados de treino.")
172
 
173
  # TAB 2: Performance
174
  with tab2:
175
+ st.subheader("Performance do Modelo (XGBoost)")
176
 
177
  y_pred = model.predict(X_test)
178
  y_proba = model.predict_proba(X_test)[:, 1]
179
 
180
+ c1, c2, c3, c4 = st.columns(4)
181
+ c1.metric("AUC Score", f"{roc_auc_score(y_test, y_proba):.3f}")
182
+ c2.metric("Recall (Segurança)", f"{recall_score(y_test, y_pred):.3f}")
183
+ c3.metric("Precisão", f"{precision_score(y_test, y_pred):.3f}")
184
+ c4.metric("Acurácia", f"{accuracy_score(y_test, y_pred):.3f}")
185
 
186
  st.markdown("### Matriz de Confusão")
187
  cm = confusion_matrix(y_test, y_pred)
188
  fig_cm = px.imshow(cm, text_auto=True, color_continuous_scale='Blues',
189
  labels=dict(x="Predito", y="Real", color="Qtd"),
190
  x=['Good', 'Bad'], y=['Good', 'Bad'])
191
+ st.plotly_chart(fig_cm, use_container_width=True)
192
 
193
+ # TAB 3: SHAP (Com Correção de Erro)
194
  with tab3:
195
+ st.subheader("Interpretabilidade do Modelo")
196
 
197
  try:
198
+ # Tenta criar o explainer padrão
 
199
  explainer = shap.TreeExplainer(model)
200
  shap_values = explainer.shap_values(X_test)
201
+
202
+ st.markdown("**1. Impacto Global das Variáveis**")
203
+
204
+ # Matplotlib Figure explícita para evitar warnings
205
+ fig, ax = plt.subplots()
206
+ shap.summary_plot(shap_values, X_test, show=False)
207
+ st.pyplot(fig)
208
+ plt.clf() # Limpar figura
209
+
210
+ st.markdown("---")
211
+ st.markdown("**2. Análise Local (Waterfall)**")
212
+
213
+ idx = st.number_input("ID do Cliente para auditar:", 0, len(X_test)-1, 0)
214
+
215
+ real_txt = 'Bad' if y_test.iloc[idx] == 1 else 'Good'
216
+ pred_txt = 'Bad' if y_pred[idx] == 1 else 'Good'
217
+ st.info(f"Cliente {idx}: Real = {real_txt} | Predito = {pred_txt}")
218
+
219
+ fig_waterfall = plt.figure()
220
+ shap.plots.waterfall(shap.Explanation(values=shap_values[idx],
221
+ base_values=explainer.expected_value,
222
+ data=X_test.iloc[idx],
223
+ feature_names=X_test.columns.tolist()),
224
+ max_display=10, show=False)
225
+ st.pyplot(fig_waterfall)
226
+ plt.clf()
227
+
228
  except Exception as e:
229
+ # Fallback para o erro de versão XGBoost/SHAP
230
  if "could not convert string to float" in str(e):
231
+ st.warning("⚠️ Ajustando visualização SHAP (Modo de Compatibilidade)...")
232
+ try:
233
+ # Usa o booster interno
234
+ explainer = shap.TreeExplainer(model.get_booster())
235
+ shap_values = explainer.shap_values(X_test)
236
+
237
+ fig, ax = plt.subplots()
238
+ shap.summary_plot(shap_values, X_test, show=False)
239
+ st.pyplot(fig)
240
+ except:
241
+ st.error("Não foi possível gerar os gráficos SHAP devido a incompatibilidade de versão.")
242
  else:
243
+ st.error(f"Erro ao calcular SHAP: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
  # TAB 4: Clusters
246
  with tab4:
247
+ st.subheader("Segmentação (KMeans & DBSCAN)")
248
 
249
+ with st.spinner("Calculando clusters..."):
250
  # KMeans
251
  kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
252
  clusters = kmeans.fit_predict(X_test)
 
255
  dbscan = DBSCAN(eps=3.0, min_samples=5)
256
  outliers = dbscan.fit_predict(X_test)
257
 
258
+ # PCA 2D
259
  pca = PCA(n_components=2)
260
  components = pca.fit_transform(X_test)
261
 
 
265
  df_viz['Status Real'] = y_test.values
266
  df_viz['Status Real'] = df_viz['Status Real'].map({0: 'Good', 1: 'Bad'})
267
 
 
268
  fig_cluster = px.scatter(df_viz, x='PC1', y='PC2', color='Cluster',
269
  symbol='Status Real',
270
  title="Mapa de Segmentação de Risco",
271
  color_discrete_sequence=px.colors.qualitative.Safe)
272
+ st.plotly_chart(fig_cluster, use_container_width=True)
273
 
274
+ num_outliers = sum(outliers == -1)
275
+ st.error(f"🚨 **DBSCAN:** Foram detectadas {num_outliers} anomalias (Outliers) que exigem revisão manual.")
276
 
277
+ # TAB 5: Relatório
278
  with tab5:
279
+ st.subheader("📋 Relatório Gerencial")
 
280
  st.markdown("""
281
+ ### Diagnóstico & Estratégia
 
 
 
 
 
 
 
 
282
 
283
+ 1. **Modelo Selecionado:** XGBoost (Foco em Recall).
284
+ - Identifica a maioria dos inadimplentes, protegendo o capital da CrediFast.
 
 
 
285
 
286
+ 2. **Fatores de Risco (SHAP):**
287
+ - **Renda Comprometida:** >30% é crítico.
288
+ - **Histórico Negativo:** Maior preditor isolado.
289
+ - **Juros:** Taxas muito altas atraem maus pagadores.
290
+
291
+ 3. **Plano de Ação:**
292
+ - [x] Implementar trava automática para parcela > 30% da renda.
293
+ - [x] Criar esteira de aprovação manual para o **Cluster de Risco**.
294
+ - [x] Revisar política de juros para clientes 'Good' para aumentar retenção.
295
  """)
296
 
297
  else:
298
+ st.error("🚨 Arquivo `credit_risk_dataset.csv` não encontrado.")
299
+ st.info("Por favor, certifique-se de que o arquivo CSV está na mesma pasta (Files) do Hugging Face.")