jaimalklyde commited on
Commit
f4bc6f4
·
verified ·
1 Parent(s): a984556

Update src/module/Forecasting.py

Browse files
Files changed (1) hide show
  1. src/module/Forecasting.py +82 -72
src/module/Forecasting.py CHANGED
@@ -139,12 +139,12 @@ class HospitalForecaster:
139
  return future_dates, y_future
140
 
141
  # ============================================================================
142
- # INTERFACE STREAMLIT
143
  # ============================================================================
144
 
145
  def show_forecasting_module(client, sheet_name):
146
  st.markdown("## 🔮 ORACLE : FORECASTING & AUDIT")
147
- st.caption("Comparaison de modèles (RF, XGB, LGBM) et projection temporelle.")
148
 
149
  if 'ml_features' not in st.session_state:
150
  st.error("❌ Données ML absentes. Allez dans 'Features Eng.' d'abord.")
@@ -152,16 +152,16 @@ def show_forecasting_module(client, sheet_name):
152
 
153
  df = st.session_state['ml_features']
154
 
155
- # --- 1. CONFIGURATION ---
156
- with st.expander("⚙️ Configuration de l'Entraînement", expanded=False):
157
  col1, col2 = st.columns(2)
158
  target = col1.selectbox("Cible à Prédire", ['nb_admissions', 'taux_occupation'], index=0)
159
- test_days = col2.slider("Jours de Test (Audit)", 30, 90, 60)
160
 
161
  # --- 2. ENTRAÎNEMENT (AUTO) ---
162
  if 'forecaster' not in st.session_state:
163
  forecaster = HospitalForecaster(df)
164
- with st.spinner("🧠 Entraînement et Comparaison des 3 Champions..."):
165
  forecaster.prepare_data(target_col=target, test_days=test_days)
166
  metrics = forecaster.train_models()
167
  st.session_state['forecaster'] = forecaster
@@ -171,96 +171,106 @@ def show_forecasting_module(client, sheet_name):
171
  forecaster = st.session_state['forecaster']
172
  metrics = st.session_state['metrics']
173
  best = forecaster.best_model_name
 
174
 
175
- # --- 3. AFFICHAGE PAR ONGLETS (Architecture Pro) ---
176
- tab_future, tab_audit = st.tabs(["🔮 Prévision Future (Décision)", "🛠️ Audit Modèles (Data Science)"])
177
 
178
- # === ONGLET 1 : LE FUTUR (Ce que veut le directeur) ===
179
  with tab_future:
180
- st.markdown(f"### 🚀 Projection : {target}")
181
 
182
- # Sélecteur d'horizon dynamique
183
- horizon_label = st.radio("Horizon de Prévision", ["3 Mois", "6 Mois", "9 Mois", "12 Mois"], horizontal=True)
184
  months_map = {"3 Mois": 3, "6 Mois": 6, "9 Mois": 9, "12 Mois": 12}
185
 
186
- # Calcul (Cache Optimisé)
187
- with st.spinner("Calcul de la trajectoire..."):
188
  f_dates, f_values = forecaster.generate_future_forecast(months_map[horizon_label])
189
 
190
  # Graphique Futur
191
  fig = go.Figure()
192
- # Historique récent
193
- hist = df.iloc[-90:]
194
- fig.add_trace(go.Scatter(x=hist['date'], y=hist[target], name="Historique", line=dict(color='gray'), opacity=0.5))
195
- # Prédiction
196
- fig.add_trace(go.Scatter(x=f_dates, y=f_values, name=f"Prévision ({best})", line=dict(color='#00E676', width=3)))
197
 
198
- fig.update_layout(title=f"Horizon {horizon_label}", template="plotly_dark", height=450)
 
 
 
 
 
 
 
 
 
 
199
  st.plotly_chart(fig, use_container_width=True)
200
 
201
- st.info(f"🏆 Modèle utilisé : **{best}** (Le plus performant sur les données de test)")
202
 
203
- # === ONGLET 2 : L'AUDIT (Ce que tu as demandé : Comparaison 3 modèles) ===
204
  with tab_audit:
205
- st.markdown("### 🥊 Comparaison : 3 Modèles vs Réalité")
206
- st.caption("Sur les données de test (Passé récent inconnu des modèles)")
 
 
 
 
 
 
 
 
 
 
 
207
 
208
- # 1. Graphique Comparatif (La demande spécifique)
209
  fig_comp = go.Figure()
210
 
211
- # La Réalité (Noir/Blanc)
212
  fig_comp.add_trace(go.Scatter(
213
  x=forecaster.dates_test, y=forecaster.y_test,
214
- name="RÉALITÉ", line=dict(color='white', width=3, dash='dot')
215
  ))
216
 
217
- # Les 3 Modèles
218
- colors = {'XGBoost': '#00d2ff', 'LightGBM': '#3af27e', 'Random Forest': '#ffdf3d'}
 
 
 
 
 
 
 
 
219
  for name, pred in forecaster.test_predictions.items():
220
- is_best = (name == best)
221
- fig_comp.add_trace(go.Scatter(
222
- x=forecaster.dates_test, y=pred,
223
- name=f"{name} {'🏆' if is_best else ''}",
224
- line=dict(color=colors.get(name, 'gray'), width=2 if is_best else 1),
225
- opacity=1 if is_best else 0.5
226
- ))
227
 
228
- fig_comp.update_layout(template="plotly_dark", height=500, title="Test de Vérité (Backtesting)")
229
  st.plotly_chart(fig_comp, use_container_width=True)
230
 
231
- # 2. Tableau des Métriques
232
- col_m1, col_m2 = st.columns([1, 1])
233
- with col_m1:
234
- st.markdown("#### 📉 Scores de Précision")
235
- metrics_df = pd.DataFrame(metrics).T.sort_values('RMSE')
236
- st.dataframe(metrics_df.style.highlight_min(subset=['RMSE', 'MAE'], color='#004d40'), use_container_width=True)
237
-
238
- # 3. SHAP (Interprétabilité)
239
- with col_m2:
240
- st.markdown("#### 🧠 Analyse SHAP (Pourquoi ?)")
241
  if SHAP_AVAILABLE:
242
- if st.button("Calculer Importance Features (SHAP)"):
243
- with st.spinner("Analyse des vecteurs d'influence..."):
244
- explainer, shap_vals, X_samp = forecaster.compute_shap()
245
- if shap_vals is not None:
246
- # On utilise matplotlib pour SHAP summary plot
247
- fig_shap, ax = plt.subplots(figsize=(6, 4))
248
- # Fond sombre pour aller avec Gotham
249
- fig_shap.patch.set_facecolor('#0e1117')
250
- ax.set_facecolor('#0e1117')
251
-
252
- shap.summary_plot(shap_vals, X_samp, max_display=10, plot_type="bar", show=False)
253
- # Customisation couleur texte
254
- ax.tick_params(colors='white')
255
- ax.xaxis.label.set_color('white')
256
- ax.yaxis.label.set_color('white')
257
-
258
- st.pyplot(fig_shap)
259
- else:
260
- st.warning("Calcul SHAP impossible sur ce modèle.")
261
- else:
262
- st.warning("Bibliothèque SHAP non installée.")
263
-
264
- if st.button("♻️ Réinitialiser l'entraînement"):
265
- del st.session_state['forecaster']
266
- st.rerun()
 
139
  return future_dates, y_future
140
 
141
  # ============================================================================
142
+ # INTERFACE STREAMLIT (UPDATE : NON-TECH FRIENDLY)
143
  # ============================================================================
144
 
145
  def show_forecasting_module(client, sheet_name):
146
  st.markdown("## 🔮 ORACLE : FORECASTING & AUDIT")
147
+ st.caption("Intelligence Artificielle de prédiction des flux hospitaliers.")
148
 
149
  if 'ml_features' not in st.session_state:
150
  st.error("❌ Données ML absentes. Allez dans 'Features Eng.' d'abord.")
 
152
 
153
  df = st.session_state['ml_features']
154
 
155
+ # --- 1. CONFIGURATION RAPIDE ---
156
+ with st.expander("⚙️ Paramètres", expanded=False):
157
  col1, col2 = st.columns(2)
158
  target = col1.selectbox("Cible à Prédire", ['nb_admissions', 'taux_occupation'], index=0)
159
+ test_days = col2.slider("Période de Test (Jours)", 30, 90, 60)
160
 
161
  # --- 2. ENTRAÎNEMENT (AUTO) ---
162
  if 'forecaster' not in st.session_state:
163
  forecaster = HospitalForecaster(df)
164
+ with st.spinner("🧠 Le système compare 3 intelligences (XGBoost, LightGBM, RF)..."):
165
  forecaster.prepare_data(target_col=target, test_days=test_days)
166
  metrics = forecaster.train_models()
167
  st.session_state['forecaster'] = forecaster
 
171
  forecaster = st.session_state['forecaster']
172
  metrics = st.session_state['metrics']
173
  best = forecaster.best_model_name
174
+ best_mae = metrics[best]['MAE']
175
 
176
+ # --- 3. AFFICHAGE PAR ONGLETS ---
177
+ tab_future, tab_audit = st.tabs(["🔮 Le Futur (Décision)", "⚖️ Le Verdict (Preuve)"])
178
 
179
+ # === ONGLET 1 : LE FUTUR ===
180
  with tab_future:
181
+ st.markdown(f"### 🚀 Projections : {target}")
182
 
183
+ # Sélecteur d'horizon
184
+ horizon_label = st.radio("Voir dans...", ["3 Mois", "6 Mois", "9 Mois", "12 Mois"], horizontal=True)
185
  months_map = {"3 Mois": 3, "6 Mois": 6, "9 Mois": 9, "12 Mois": 12}
186
 
187
+ with st.spinner("Calcul de la trajectoire optimale..."):
 
188
  f_dates, f_values = forecaster.generate_future_forecast(months_map[horizon_label])
189
 
190
  # Graphique Futur
191
  fig = go.Figure()
192
+ hist = df.iloc[-90:] # 3 derniers mois historiques
 
 
 
 
193
 
194
+ # Zone Historique
195
+ fig.add_trace(go.Scatter(x=hist['date'], y=hist[target], name="Passé Réel", line=dict(color='gray'), opacity=0.5))
196
+
197
+ # Zone Futur
198
+ fig.add_trace(go.Scatter(x=f_dates, y=f_values, name="Projection IA", line=dict(color='#00E676', width=3)))
199
+
200
+ # Ligne verticale "Aujourd'hui"
201
+ fig.add_vline(x=hist['date'].max(), line_width=1, line_dash="dash", line_color="white")
202
+ fig.add_annotation(x=hist['date'].max(), y=hist[target].mean(), text="Aujourd'hui", showarrow=True, arrowhead=1)
203
+
204
+ fig.update_layout(title=f"Horizon {horizon_label}", template="plotly_dark", height=450, hovermode="x unified")
205
  st.plotly_chart(fig, use_container_width=True)
206
 
207
+ st.caption(f"Prédiction générée par le modèle **{best}** (Fiabilité : {metrics[best]['R2']*100:.0f}%)")
208
 
209
+ # === ONGLET 2 : LE VERDICT (Pour les non-techs) ===
210
  with tab_audit:
211
+ st.markdown("### 🏆 Qui est le meilleur prédicteur ?")
212
+
213
+ # --- A. LE VERDICT EN FRANÇAIS ---
214
+ st.info(f"""
215
+ **Résultat du match :** L'intelligence artificielle **{best}** a battu les autres modèles.
216
+
217
+ 🔎 **Pourquoi ?** Sur les {test_days} derniers jours, elle a suivi la courbe réelle avec le plus de précision.
218
+ En moyenne, elle se trompe de seulement **{best_mae:.1f}** unités par jour (contre {metrics['Random Forest']['MAE']:.1f} pour le moins bon).
219
+ """)
220
+
221
+ # --- B. PREUVE VISUELLE ---
222
+ st.markdown("#### 👁️ Preuve Visuelle : Modèles vs Réalité")
223
+ st.caption("Regardez quelle ligne colorée suit le mieux la ligne blanche pointillée (La Réalité).")
224
 
 
225
  fig_comp = go.Figure()
226
 
227
+ # 1. La Réalité (Blanche, très visible)
228
  fig_comp.add_trace(go.Scatter(
229
  x=forecaster.dates_test, y=forecaster.y_test,
230
+ name="🔴 LA RÉALITÉ", line=dict(color='white', width=4, dash='dot')
231
  ))
232
 
233
+ # 2. Le Vainqueur (Vert fluo)
234
+ best_pred = forecaster.test_predictions[best]
235
+ fig_comp.add_trace(go.Scatter(
236
+ x=forecaster.dates_test, y=best_pred,
237
+ name=f"🏆 {best} (Gagnant)",
238
+ line=dict(color='#00E676', width=3),
239
+ opacity=0.9
240
+ ))
241
+
242
+ # 3. Les Perdants (Grisés pour ne pas polluer)
243
  for name, pred in forecaster.test_predictions.items():
244
+ if name != best:
245
+ fig_comp.add_trace(go.Scatter(
246
+ x=forecaster.dates_test, y=pred,
247
+ name=name,
248
+ line=dict(color='gray', width=1),
249
+ opacity=0.4
250
+ ))
251
 
252
+ fig_comp.update_layout(template="plotly_dark", height=450)
253
  st.plotly_chart(fig_comp, use_container_width=True)
254
 
255
+ # --- C. SHAP (Bonus Explicatif) ---
256
+ col_why, col_reset = st.columns([3, 1])
257
+ with col_why:
 
 
 
 
 
 
 
258
  if SHAP_AVAILABLE:
259
+ with st.expander("🧠 Comprendre le cerveau de l'IA (Pourquoi ces chiffres ?)"):
260
+ st.write("Ce graphique montre ce qui influence le plus la hausse ou la baisse des admissions :")
261
+ if st.button("Calculer les Facteurs d'Influence"):
262
+ with st.spinner("Analyse..."):
263
+ explainer, shap_vals, X_samp = forecaster.compute_shap()
264
+ if shap_vals is not None:
265
+ fig_shap, ax = plt.subplots(figsize=(8, 5))
266
+ fig_shap.patch.set_facecolor('#0e1117')
267
+ ax.set_facecolor('#0e1117')
268
+ shap.summary_plot(shap_vals, X_samp, max_display=8, plot_type="bar", show=False)
269
+ ax.tick_params(colors='white', labelsize=10)
270
+ ax.xaxis.label.set_color('white')
271
+ st.pyplot(fig_shap)
272
+
273
+ with col_reset:
274
+ if st.button("♻️ Reset"):
275
+ del st.session_state['forecaster']
276
+ st.rerun()