Djohell commited on
Commit
4916cd3
·
verified ·
1 Parent(s): 8b9e5d1

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +361 -0
app.py ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #-------------------------------------------------------- Imports nécessaires ---------------------------------------------------
2
+ import os
3
+ import warnings
4
+ import pandas as pd
5
+ import numpy as np
6
+ import plotly.express as px
7
+ import streamlit as st
8
+ import boto3
9
+ import mlflow
10
+ import mlflow.pyfunc
11
+
12
+ from sklearn.exceptions import UndefinedMetricWarning
13
+ from sklearn import set_config
14
+
15
+ warnings.filterwarnings("ignore", category=UndefinedMetricWarning)
16
+ set_config(display="text")
17
+
18
+ #-------------------------------------------------------- Configuration AWS S3 ---------------------------------------------------
19
+ aws_access_key_id = os.environ.get("AWS_ACCESS_KEY_ID")
20
+ aws_secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
21
+ aws_region = "eu-west-3"
22
+
23
+ s3 = boto3.client(
24
+ 's3',
25
+ region_name=aws_region,
26
+ aws_access_key_id=aws_access_key_id,
27
+ aws_secret_access_key=aws_secret_access_key
28
+ )
29
+
30
+ #-------------------------------------------------------- Streamlit page config ---------------------------------------------------
31
+ st.set_page_config(page_title="Projet Incendies", layout="wide")
32
+
33
+ #-------------------------------------------------------- Sidebar navigation ---------------------------------------------------
34
+ st.sidebar.title("Navigation")
35
+ page = st.sidebar.radio("Aller à", [
36
+ "Accueil",
37
+ "Notre Projet",
38
+ "Résultats des modèles",
39
+ ])
40
+
41
+ #-------------------------------------------------------- Footer ---------------------------------------------------
42
+ def show_footer():
43
+ st.markdown("---")
44
+ st.markdown("Projet réalisé dans le cadre de la formation Lead Data Scientist. © 2025")
45
+
46
+ #-------------------------------------------------------- Chargement des datasets depuis S3 ---------------------------------------------------
47
+ @st.cache_data(show_spinner="🔄 Téléchargement des dernières données météo…", ttl=None)
48
+ def load_model_data() -> pd.DataFrame:
49
+ bucket = "projet-final-lead"
50
+ key = "data/dataset_complet_meteo.csv"
51
+ try:
52
+ obj = s3.get_object(Bucket=bucket, Key=key)
53
+ df = pd.read_csv(obj['Body'], sep=';')
54
+ for col in df.columns:
55
+ if "date" in col.lower():
56
+ df[col] = pd.to_datetime(df[col], errors="coerce", dayfirst=True)
57
+ return df
58
+ except Exception as e:
59
+ st.error(f"❌ Erreur lors du chargement du dataset modèle : {e}")
60
+ return pd.DataFrame()
61
+
62
+ @st.cache_data(show_spinner="🔄 Téléchargement du dataset historique…", ttl=None)
63
+ def load_historique_incendies() -> pd.DataFrame:
64
+ bucket = "projet-final-lead"
65
+ key = "data/historique_incendies_avec_coordonnees.csv"
66
+ try:
67
+ obj = s3.get_object(Bucket=bucket, Key=key)
68
+ df = pd.read_csv(obj['Body'], sep=';', encoding='utf-8')
69
+ return df
70
+ except Exception as e:
71
+ st.error(f"❌ Erreur lors du chargement du dataset historique : {e}")
72
+ return pd.DataFrame()
73
+
74
+ #-------------------------------------------------------- MLflow ---------------------------------------------------
75
+ mlflow.set_tracking_uri("https://djohell-ml-flow.hf.space")
76
+
77
+ @st.cache_data(show_spinner="🔄 Chargement du modèle prédictif…", ttl=None)
78
+ def load_mlflow_model():
79
+ model_uri = 'runs:/69a3c889954f4ce9a2139a4fb4cefc59/survival_xgb_model'
80
+ model = mlflow.pyfunc.load_model(model_uri)
81
+ return model
82
+
83
+ _model = load_mlflow_model()
84
+ st.write("✅ Modèle prédictif chargé avec nos dernières prévisions !")
85
+
86
+ #-------------------------------------------------------- Prédiction via MLflow ---------------------------------------------------
87
+ @st.cache_data(show_spinner="⚙️ Prédiction des risques…", ttl=None)
88
+ def predict_risk(df_raw: pd.DataFrame, _model) -> pd.DataFrame:
89
+ df = df_raw.copy()
90
+ df = df.rename(columns={"Feu prévu": "event", "décompte": "duration"})
91
+ df["event"] = df["event"].astype(bool)
92
+ df["duration"] = df["duration"].fillna(0)
93
+
94
+ features = [
95
+ "moyenne precipitations mois", "moyenne temperature mois",
96
+ "moyenne evapotranspiration mois", "moyenne vitesse vent année",
97
+ "moyenne vitesse vent mois", "moyenne temperature année",
98
+ "RR", "UM", "ETPMON", "TN", "TX", "Nombre de feu par an",
99
+ "Nombre de feu par mois", "jours_sans_pluie", "jours_TX_sup_30",
100
+ "ETPGRILLE_7j", "compteur jours vers prochain feu",
101
+ "compteur feu log", "Année", "Mois",
102
+ "moyenne precipitations année", "moyenne evapotranspiration année",
103
+ ]
104
+ features = [f for f in features if f in df.columns]
105
+
106
+ log_hr_all = _model.predict(df[features])
107
+ HR = np.exp(log_hr_all)
108
+
109
+ def S0(t):
110
+ return np.exp(-t/1000)
111
+
112
+ horizons = {7:"proba_7j", 30:"proba_30j", 60:"proba_60j", 90:"proba_90j", 180:"proba_180j"}
113
+ for t, col in horizons.items():
114
+ df[col] = 1 - (S0(t) ** HR)
115
+
116
+ for col in ["latitude","longitude","ville"]:
117
+ if col not in df.columns:
118
+ df[col] = np.nan
119
+
120
+ df_map = df[["latitude","longitude","ville"] + list(horizons.values())].copy()
121
+ return df_map
122
+
123
+ #-------------------------------------------------------- Page Accueil ---------------------------------------------------
124
+ if page == "Accueil":
125
+ st.title("Carte du risque d’incendie en Corse")
126
+
127
+ df_raw = load_model_data()
128
+ df_map = predict_risk(df_raw, _model)
129
+
130
+ horizons_lbl = {
131
+ "7 jours":"proba_7j",
132
+ "30 jours":"proba_30j",
133
+ "60 jours":"proba_60j",
134
+ "90 jours":"proba_90j",
135
+ "180 jours":"proba_180j",
136
+ }
137
+ choix = st.radio("Choisissez l’horizon temporel souhaité :", list(horizons_lbl.keys()), horizontal=True, index=0)
138
+ col_proba = horizons_lbl[choix]
139
+
140
+ vmax = float(df_map[col_proba].max())
141
+ fig = px.scatter_mapbox(
142
+ df_map,
143
+ lat="latitude",
144
+ lon="longitude",
145
+ hover_name="ville",
146
+ hover_data={col_proba: ":.2%"},
147
+ color=col_proba,
148
+ color_continuous_scale="YlOrRd",
149
+ range_color=(0.0, vmax),
150
+ zoom=7,
151
+ height=650,
152
+ )
153
+ fig.update_layout(
154
+ mapbox_style="open-street-map",
155
+ margin=dict(l=0,r=0,t=0,b=0),
156
+ coloraxis_colorbar=dict(title="Probabilité", tickformat=".0%"),
157
+ )
158
+ st.subheader(f"Risque d’incendie – horizon **{choix}**")
159
+ st.plotly_chart(fig, use_container_width=True)
160
+
161
+ show_footer()
162
+
163
+ #-------------------------------------------------------- Page Notre Projet ---------------------------------------------------
164
+ elif page == "Notre Projet":
165
+ st.title("🔥 Projet Analyse des Incendies 🔥")
166
+
167
+ st.subheader(" 📊 Contexte")
168
+ st.subheader("🌲La forêt française en chiffres")
169
+ col1, col2 = st.columns([2,1])
170
+ with col1:
171
+ st.markdown("""
172
+ La France est le 4ᵉ pays européen en superficie forestière, avec **17,5 millions d’hectares** en métropole (32 % du territoire) et **8 millions** en Guyane.
173
+ Au total, les forêts couvrent environ **41 %** du territoire national.
174
+ - **75 %** des forêts sont privées (3,5 millions de propriétaires).
175
+ - **16 %** publiques (collectivités).
176
+ - **9 %** domaniales (État).
177
+ La forêt française est un réservoir de biodiversité :
178
+ - **190 espèces d’arbres** (67 % feuillus, 33 % conifères).
179
+ - **73 espèces de mammifères**, **120 d’oiseaux**.
180
+ - Environ **30 000 espèces de champignons et autant d’insectes**.
181
+ - **72 %** de la flore française se trouve en forêt.
182
+ Les forêts françaises absorbent environ **9 %** des émissions nationales de gaz à effet de serre, jouant un rôle crucial dans la lutte contre le changement climatique.
183
+ Le Code forestier encadre leur gestion durable pour protéger la biodiversité, l’air, l’eau et prévenir les risques naturels.
184
+ """)
185
+
186
+ st.header("🔥 Corse : Bilan Campagne Feux de Forêts 2024")
187
+ tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs(["📌 Contexte","🛠️ Prévention","🚒 Moyens","📊 Statistiques","🔍 Causes","🔎 Enquêtes"])
188
+
189
+ # Contenus des tabs
190
+ with tab1:
191
+ with st.expander("📌 Contexte général"):
192
+ st.markdown("""
193
+ - **80 %** de la Corse est couverte de forêts/maquis → **fort risque incendie**
194
+ - **2023-2024** : la plus chaude et la plus sèche jamais enregistrée
195
+ - **714 mm** de pluie sur l’année (**78 %** de la normale)
196
+ - **Façade orientale** : seulement **30 %** des précipitations normales
197
+ """)
198
+
199
+ with tab2:
200
+ with st.expander("🛠️ Prévention & Investissements"):
201
+ st.markdown("""
202
+ - **1,9 million €** investis en 2023-2024 par l’État (jusqu’à 80 % de financement)
203
+ - Travaux financés :
204
+ - Pistes DFCI/DECI (Sorio di Tenda, Oletta, Île-Rousse…)
205
+ - Citernes souples & points d’eau
206
+ - Drones, caméras thermiques, logiciels SIG
207
+ - Véhicules pour réserves communales
208
+ """)
209
+
210
+ with tab3:
211
+ with st.expander("🚒 Moyens déployés"):
212
+ st.markdown("""
213
+ - Jusqu’à **500 personnels mobilisables**
214
+ - **168 sapeurs-pompiers SIS2B**, **261 UIISC5**, forestiers-sapeurs, gendarmerie, ONF…
215
+ - Moyens aériens :
216
+ - **1 hélico**, **2 canadairs** à Ajaccio
217
+ - **12 canadairs** + **8 Dashs** nationaux en renfort
218
+ """)
219
+
220
+ with tab4:
221
+ with st.expander("📊 Statistiques Feux Été 2024"):
222
+ st.markdown("""
223
+ - **107 feux** recensés (~9/semaine)
224
+ - **130 ha** brûlés dont :
225
+ - 83 % des feux <1 ha : **5,42 ha**
226
+ - 4 gros feux >10 ha : **72,84 ha**
227
+ - Linguizetta (**22,19 ha**), Oletta (**18,9 ha**), Pioggiola (**18,75 ha**), Tallone (**13 ha**)
228
+ - Depuis janvier 2024 : **285 feux** pour **587 ha**
229
+ - Feu majeur à Barbaggio : **195 ha (33 % du total annuel)**
230
+ """)
231
+
232
+ with tab5:
233
+ with st.expander("🔍 Causes des feux (38 cas identifiés)"):
234
+ st.markdown("""
235
+ - **11** : foudre
236
+ - **8** : écobuages
237
+ - **6** : malveillance
238
+ - **5** : accidents
239
+ - **4** : mégots de cigarette
240
+ """)
241
+ with st.expander("⚠️ Prévention = priorité absolue"):
242
+ st.markdown("""
243
+ - **90 %** des feux ont une origine humaine
244
+ - Causes principales : **imprudences** (mégots, BBQ, travaux, écobuages…)
245
+ """)
246
+
247
+ with tab6:
248
+ with st.expander("🔎 Enquêtes & Surveillance"):
249
+ st.markdown("""
250
+ - **20 incendies** étudiés par la Cellule Technique d’Investigation (CTIFF)
251
+ - Équipes mobilisées : **7 forestiers**, **15 pompiers**, **21 forces de l’ordre**
252
+ - **Fermeture de massif** enclenchée 1 seule fois : forêt de Pinia
253
+ """)
254
+
255
+ #---------------------------------------------------Notre Objectif --------------------------------------------------------
256
+
257
+ st.subheader("🎯 Notre Objectif")
258
+ st.markdown("""
259
+ Dans un contexte de **changement climatique** et de **risques accrus d’incendies de forêt**, notre équipe a développé un projet innovant visant à **analyser et prédire les zones à risque d’incendie** en France, avec un focus particulier sur la **Corse**.
260
+ """)
261
+ #---------------------------------------------------Obectifs du projet---------------------------------------------------
262
+ col1, col2 = st.columns([1, 1])
263
+ with col1:
264
+ st.subheader("🔍 Exploration des données")
265
+ st.markdown("""
266
+ - ✅ **Évolution du nombre d’incendies**, répartition par mois et par causes.
267
+ - ✅ **Cartographie interactive** des incendies sur tout le territoire.
268
+ - ✅ **Analyse des clusters** grâce à DBSCAN pour identifier les zones les plus à risque.
269
+ """)
270
+
271
+ with col2:
272
+ st.subheader("📈 Modèles prédictifs")
273
+ st.markdown("""
274
+ - ✅ **Comparaison des modèles** : Random Forest, XGBoost, analyse de survie.
275
+ - ✅ **Prédiction des zones à risque** avec visualisation sur carte.
276
+ - ✅ Fourniture d'un **outil décisionnel** pour les autorités et les services de gestion des risques.
277
+ """)
278
+
279
+ st.subheader("📘 Définition de l'analyse de survie (Survival Analysis")
280
+ col1, col2, col3, col4 = st.columns(4)
281
+
282
+ with col1:
283
+ st.markdown("### 🧠 Qu’est-ce que l’analyse de survie ?")
284
+ st.markdown("""
285
+ L’**analyse de survie** (ou **Survival Analysis**) est une méthode statistique utilisée pour **modéliser le temps avant qu’un événement se produise**, comme :
286
+ - 🔥 un incendie,
287
+ - 🏥 un décès,
288
+ - 📉 une résiliation d’abonnement,
289
+ - 🧯 une panne.
290
+ """)
291
+
292
+ with col2:
293
+ st.markdown("### 📌 Objectif :")
294
+ st.markdown("""
295
+ > Estimer la **probabilité qu’un événement ne se soit pas encore produit** à un instant donné.
296
+ """)
297
+
298
+ with col3:
299
+ st.markdown("### 🔑 Concepts fondamentaux : ")
300
+ st.markdown("""
301
+ - ⏳ **Temps de survie (`T`)** : temps écoulé jusqu’à l’événement.
302
+ - 🎯 **Événement** : le phénomène qu’on cherche à prédire (feu, panne, décès...).
303
+ - ❓ **Censure** : l’événement **n’a pas encore eu lieu** durant la période d’observation.
304
+ - 📉 **Fonction de survie `S(t)`** : probabilité de "survivre" après le temps `t`.
305
+ - ⚠️ **Fonction de risque `h(t)`** : probabilité que l’événement se produise **immédiatement après `t`**, sachant qu’il ne s’est pas encore produit.
306
+ """)
307
+
308
+ with col4:
309
+ st.markdown ("### 🧪 Exemples d’applications :")
310
+ st.markdown("""
311
+ | Domaine | Exemple |
312
+ |--------|---------|
313
+ | 🔥 Incendies | Quand un feu va-t-il se déclarer ? |
314
+ | 🏥 Santé | Combien de temps un patient survivra après traitement ? |
315
+ | 📉 Marketing | Quand un client risque-t-il de partir ? |
316
+ | 🧑‍💼 RH | Quand un salarié quittera-t-il l’entreprise ? |
317
+ """)
318
+
319
+ #---------------------------------------------------Equipe du projet---------------------------------------------------
320
+ st.subheader("👨‍💻 Équipe du projet")
321
+ col1, col2, col3, col4, col5, col6 = st.columns(6)
322
+ with col1:
323
+ st.image("images/Francois_Minaret.jpg", width=150)
324
+ st.markdown("**Francois Minaret**")
325
+ with col2:
326
+ st.image("images/Joel_Termondjian.jpg", width=150)
327
+ st.markdown("**Joël Termondjian**")
328
+ with col3:
329
+ st.image("images/Marc_Barthes.jpg", width=150)
330
+ st.markdown("**Marc Barthes**")
331
+ with col4:
332
+ st.image("images/Gilles_Akakpo.jpg", width=150)
333
+ st.markdown("**Gilles Akakpo**")
334
+ with col5:
335
+ st.image("images/Nathalie_Devogelaere.jpg", width=150)
336
+ st.markdown("**Nathalie Devogelaere**")
337
+ with col6:
338
+ st.image("images/David_Jaoui.jpg", width=150)
339
+ st.markdown("**David Jaoui**")
340
+
341
+
342
+ show_footer()
343
+
344
+
345
+ #---------------------------------------------------- Page Résultats des modèles -----------------------------------------
346
+
347
+ elif page == "Résultats des modèles":
348
+ st.title("📈 Résultats des modèles prédictifs")
349
+ st.markdown("### Comparaison des modèles de Survival Analysis")
350
+
351
+ #--------------------------------------------------- Tableau codé en dur en Markdown -----------------------------------
352
+ st.markdown("""
353
+ | Modèle | Concordance Index |
354
+ |-----------------------------------|-------------------|
355
+ | Predict survival fonction (MVP) | 0.69 |
356
+ | XGBOOST survival cox | 0.809 |
357
+ """)
358
+
359
+ st.markdown("👉 Le modèle **XGBOOST survival cox** obtient la meilleure performance globale.")
360
+
361
+ show_footer()