Spaces:
Sleeping
Sleeping
Update src/module/Forecasting.py
Browse files- 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("
|
| 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("⚙️
|
| 157 |
col1, col2 = st.columns(2)
|
| 158 |
target = col1.selectbox("Cible à Prédire", ['nb_admissions', 'taux_occupation'], index=0)
|
| 159 |
-
test_days = col2.slider("
|
| 160 |
|
| 161 |
# --- 2. ENTRAÎNEMENT (AUTO) ---
|
| 162 |
if 'forecaster' not in st.session_state:
|
| 163 |
forecaster = HospitalForecaster(df)
|
| 164 |
-
with st.spinner("🧠
|
| 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
|
| 176 |
-
tab_future, tab_audit = st.tabs(["🔮
|
| 177 |
|
| 178 |
-
# === ONGLET 1 : LE FUTUR
|
| 179 |
with tab_future:
|
| 180 |
-
st.markdown(f"### 🚀
|
| 181 |
|
| 182 |
-
# Sélecteur d'horizon
|
| 183 |
-
horizon_label = st.radio("
|
| 184 |
months_map = {"3 Mois": 3, "6 Mois": 6, "9 Mois": 9, "12 Mois": 12}
|
| 185 |
|
| 186 |
-
|
| 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 |
-
#
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
st.plotly_chart(fig, use_container_width=True)
|
| 200 |
|
| 201 |
-
st.
|
| 202 |
|
| 203 |
-
# === ONGLET 2 :
|
| 204 |
with tab_audit:
|
| 205 |
-
st.markdown("###
|
| 206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
|
| 208 |
-
# 1. Graphique Comparatif (La demande spécifique)
|
| 209 |
fig_comp = go.Figure()
|
| 210 |
|
| 211 |
-
# La Réalité (
|
| 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=
|
| 215 |
))
|
| 216 |
|
| 217 |
-
#
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
for name, pred in forecaster.test_predictions.items():
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
|
| 228 |
-
fig_comp.update_layout(template="plotly_dark", height=
|
| 229 |
st.plotly_chart(fig_comp, use_container_width=True)
|
| 230 |
|
| 231 |
-
#
|
| 232 |
-
|
| 233 |
-
with
|
| 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 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|