Tracy André commited on
Commit
27281c3
·
1 Parent(s): 676811f
Files changed (6) hide show
  1. analyzer.py +137 -53
  2. data_loader.py +66 -41
  3. interface.py +152 -57
  4. main.py +72 -6
  5. main_fallback.py +147 -0
  6. visualizations.py +317 -145
analyzer.py CHANGED
@@ -191,77 +191,161 @@ class AgricultureAnalyzer:
191
  return risk_analysis.sort_values(['Risk_Score', 'IFT_herbicide_approx'])
192
 
193
  def get_summary_stats(self):
194
- """Retourne les statistiques de résumé"""
195
- if self.df is None:
196
- return "Aucune donnée disponible"
197
-
198
- stats_text = f"""
 
 
 
 
 
 
 
 
 
 
199
  ## 📊 Statistiques Générales
200
- - **Nombre total de parcelles**: {self.df['numparcell'].nunique()}
201
- - **Nombre d'interventions**: {len(self.df):,}
202
- - **Surface totale**: {self.df['surfparc'].sum():.2f} hectares
203
- - **Surface moyenne par parcelle**: {self.df['surfparc'].mean():.2f} hectares
204
- - **Période**: {self.df['millesime'].min()} - {self.df['millesime'].max()}
205
 
206
  ## 🧪 Analyse Herbicides
207
  """
 
 
 
 
 
208
 
209
- if 'familleprod' in self.df.columns:
210
- herbicides_df = self.df[self.df['familleprod'] == 'Herbicides']
211
- if len(herbicides_df) > 0:
212
- stats_text += f"""
213
- - **Interventions herbicides**: {len(herbicides_df)} ({(len(herbicides_df)/len(self.df)*100):.1f}%)
214
- - **Parcelles traitées**: {herbicides_df['numparcell'].nunique()}
215
- - **Produits herbicides différents**: {herbicides_df['produit'].nunique()}
216
  """
217
-
218
- if self.risk_analysis is not None and len(self.risk_analysis) > 0:
219
- risk_distribution = self.risk_analysis['Risque_adventice'].value_counts()
220
- stats_text += f"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
  ## 🎯 Répartition des Risques Adventices
223
  """
224
- for risk_level in RISK_LEVELS:
225
- if risk_level in risk_distribution:
226
- count = risk_distribution[risk_level]
227
- pct = (count / len(self.risk_analysis)) * 100
228
- stats_text += f"- **{risk_level}**: {count} parcelles ({pct:.1f}%)\n"
229
-
230
- return stats_text
 
 
 
 
 
 
 
 
 
231
 
232
  def get_low_risk_recommendations(self):
233
- """Retourne les recommandations pour les parcelles à faible risque"""
234
- if self.risk_analysis is None:
235
- return "Analyse des risques non disponible"
236
-
237
- low_risk = self.risk_analysis[
238
- self.risk_analysis['Risque_adventice'].isin(['TRÈS FAIBLE', 'FAIBLE'])
239
- ].head(10)
240
-
241
- recommendations = "## 🌾 TOP 10 - Parcelles Recommandées pour Cultures Sensibles (Pois, Haricot)\n\n"
242
-
243
- for idx, row in low_risk.iterrows():
244
- if isinstance(idx, tuple) and len(idx) >= 4:
245
- parcelle, nom, culture, surface = idx[:4]
246
- else:
247
- # Fallback si l'index n'est pas un tuple de 4 éléments
248
- parcelle = str(idx)
249
- nom = "N/A"
250
- culture = "N/A"
251
- surface = row.get('surfparc', 0) if 'surfparc' in row else 0
252
 
253
- recommendations += f"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  **Parcelle {parcelle}** ({nom})
255
  - Culture actuelle: {culture}
256
  - Surface: {surface:.2f} ha
257
- - Niveau de risque: {row['Risque_adventice']}
258
- - IFT herbicide: {row['IFT_herbicide_approx']:.2f}
259
- - Nombre d'herbicides: {row.get('Nb_herbicides', 0)}
260
 
261
  ---
262
  """
263
-
264
- return recommendations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
  def get_risk_analysis(self):
267
  """Retourne l'analyse des risques"""
 
191
  return risk_analysis.sort_values(['Risk_Score', 'IFT_herbicide_approx'])
192
 
193
  def get_summary_stats(self):
194
+ """Retourne les statistiques de résumé avec gestion d'erreur"""
195
+ try:
196
+ if self.df is None:
197
+ return "❌ Aucune donnée disponible"
198
+
199
+ # Statistiques générales avec gestion d'erreur
200
+ try:
201
+ total_parcelles = self.df['numparcell'].nunique()
202
+ total_interventions = len(self.df)
203
+ surface_totale = self.df['surfparc'].sum()
204
+ surface_moyenne = self.df['surfparc'].mean()
205
+ periode_min = self.df['millesime'].min()
206
+ periode_max = self.df['millesime'].max()
207
+
208
+ stats_text = f"""
209
  ## 📊 Statistiques Générales
210
+ - **Nombre total de parcelles**: {total_parcelles}
211
+ - **Nombre d'interventions**: {total_interventions:,}
212
+ - **Surface totale**: {surface_totale:.2f} hectares
213
+ - **Surface moyenne par parcelle**: {surface_moyenne:.2f} hectares
214
+ - **Période**: {periode_min} - {periode_max}
215
 
216
  ## 🧪 Analyse Herbicides
217
  """
218
+ except Exception as e:
219
+ print(f"❌ Erreur dans les statistiques générales: {e}")
220
+ stats_text = """
221
+ ## 📊 Statistiques Générales
222
+ ❌ Erreur lors du calcul des statistiques générales
223
 
224
+ ## 🧪 Analyse Herbicides
 
 
 
 
 
 
225
  """
226
+
227
+ # Analyse des herbicides avec gestion d'erreur
228
+ try:
229
+ if 'familleprod' in self.df.columns:
230
+ herbicides_df = self.df[self.df['familleprod'] == 'Herbicides']
231
+ if len(herbicides_df) > 0:
232
+ nb_herbicides = len(herbicides_df)
233
+ pct_herbicides = (nb_herbicides/len(self.df)*100)
234
+ parcelles_traitees = herbicides_df['numparcell'].nunique()
235
+
236
+ if 'produit' in herbicides_df.columns:
237
+ produits_uniques = herbicides_df['produit'].nunique()
238
+ stats_text += f"""
239
+ - **Interventions herbicides**: {nb_herbicides} ({pct_herbicides:.1f}%)
240
+ - **Parcelles traitées**: {parcelles_traitees}
241
+ - **Produits herbicides différents**: {produits_uniques}
242
+ """
243
+ else:
244
+ stats_text += f"""
245
+ - **Interventions herbicides**: {nb_herbicides} ({pct_herbicides:.1f}%)
246
+ - **Parcelles traitées**: {parcelles_traitees}
247
+ """
248
+ else:
249
+ stats_text += "\n- **Aucune intervention herbicide détectée**"
250
+ else:
251
+ stats_text += "\n- **Données d'herbicides non disponibles**"
252
+ except Exception as e:
253
+ print(f"❌ Erreur dans l'analyse des herbicides: {e}")
254
+ stats_text += "\n❌ Erreur lors de l'analyse des herbicides"
255
+
256
+ # Analyse des risques avec gestion d'erreur
257
+ try:
258
+ if self.risk_analysis is not None and len(self.risk_analysis) > 0:
259
+ risk_distribution = self.risk_analysis['Risque_adventice'].value_counts()
260
+ stats_text += f"""
261
 
262
  ## 🎯 Répartition des Risques Adventices
263
  """
264
+ for risk_level in RISK_LEVELS:
265
+ if risk_level in risk_distribution:
266
+ count = risk_distribution[risk_level]
267
+ pct = (count / len(self.risk_analysis)) * 100
268
+ stats_text += f"- **{risk_level}**: {count} parcelles ({pct:.1f}%)\n"
269
+ else:
270
+ stats_text += "\n\n❌ Analyse des risques non disponible"
271
+ except Exception as e:
272
+ print(f"❌ Erreur dans l'analyse des risques: {e}")
273
+ stats_text += "\n\n❌ Erreur lors de l'analyse des risques"
274
+
275
+ return stats_text
276
+
277
+ except Exception as e:
278
+ print(f"❌ Erreur critique dans get_summary_stats: {e}")
279
+ return "❌ Erreur critique lors de la génération des statistiques"
280
 
281
  def get_low_risk_recommendations(self):
282
+ """Retourne les recommandations pour les parcelles à faible risque avec gestion d'erreur"""
283
+ try:
284
+ if self.risk_analysis is None or len(self.risk_analysis) == 0:
285
+ return "❌ Analyse des risques non disponible"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
+ try:
288
+ low_risk = self.risk_analysis[
289
+ self.risk_analysis['Risque_adventice'].isin(['TRÈS FAIBLE', 'FAIBLE'])
290
+ ].head(10)
291
+
292
+ if len(low_risk) == 0:
293
+ return """## 🌾 Recommandations pour Cultures Sensibles
294
+
295
+ ❌ Aucune parcelle à faible risque trouvée.
296
+
297
+ 💡 **Suggestion**: Considérez une rotation plus longue ou des techniques alternatives pour réduire la pression adventice."""
298
+
299
+ recommendations = "## 🌾 TOP 10 - Parcelles Recommandées pour Cultures Sensibles (Pois, Haricot)\n\n"
300
+
301
+ for idx, row in low_risk.iterrows():
302
+ try:
303
+ if isinstance(idx, tuple) and len(idx) >= 4:
304
+ parcelle, nom, culture, surface = idx[:4]
305
+ else:
306
+ # Fallback si l'index n'est pas un tuple de 4 éléments
307
+ parcelle = str(idx)
308
+ nom = "N/A"
309
+ culture = "N/A"
310
+ surface = row.get('surfparc', 0) if hasattr(row, 'get') else 0
311
+
312
+ # Vérification des valeurs avec fallbacks
313
+ risque = row.get('Risque_adventice', 'N/A') if hasattr(row, 'get') else 'N/A'
314
+ ift = row.get('IFT_herbicide_approx', 0) if hasattr(row, 'get') else 0
315
+ nb_herb = row.get('Nb_herbicides', 0) if hasattr(row, 'get') else 0
316
+
317
+ recommendations += f"""
318
  **Parcelle {parcelle}** ({nom})
319
  - Culture actuelle: {culture}
320
  - Surface: {surface:.2f} ha
321
+ - Niveau de risque: {risque}
322
+ - IFT herbicide: {ift:.2f}
323
+ - Nombre d'herbicides: {nb_herb}
324
 
325
  ---
326
  """
327
+ except Exception as e:
328
+ print(f"❌ Erreur lors du traitement d'une parcelle: {e}")
329
+ recommendations += f"""
330
+ **Parcelle {str(idx)}**
331
+ ❌ Erreur lors du traitement des données de cette parcelle
332
+
333
+ ---
334
+ """
335
+
336
+ return recommendations
337
+
338
+ except Exception as e:
339
+ print(f"❌ Erreur lors de la génération des recommandations: {e}")
340
+ return """## 🌾 Recommandations pour Cultures Sensibles
341
+
342
+ ❌ Erreur lors de la génération des recommandations.
343
+
344
+ 💡 **Suggestion**: Vérifiez la qualité des données et relancez l'analyse."""
345
+
346
+ except Exception as e:
347
+ print(f"❌ Erreur critique dans get_low_risk_recommendations: {e}")
348
+ return "❌ Erreur critique lors de la génération des recommandations"
349
 
350
  def get_risk_analysis(self):
351
  """Retourne l'analyse des risques"""
data_loader.py CHANGED
@@ -15,53 +15,78 @@ class DataLoader:
15
  self.df = None
16
 
17
  def load_data(self):
18
- """Charge les données du dataset Hugging Face"""
19
- print(MESSAGES["loading"])
20
- print(f"📋 Dataset ID: {DATASET_ID}")
21
- print(f"📋 Token disponible: {'Oui' if HF_TOKEN else 'Non'}")
22
-
23
- self.df = None
24
-
25
- # 1) Tentative de chargement direct via datasets.load_dataset
26
  try:
27
- dataset = load_dataset(
28
- DATASET_ID,
29
- split="train",
30
- token=HF_TOKEN,
31
- trust_remote_code=True,
32
- )
33
- print(f"📊 Dataset chargé: {len(dataset)} exemples")
34
 
 
 
 
35
  try:
36
- self.df = dataset.to_pandas()
37
- print("✅ Conversion to_pandas() réussie")
38
- except Exception as pandas_error:
39
- print(f"❌ Erreur to_pandas(): {pandas_error}")
40
- print("🔄 Tentative de conversion manuelle...")
41
- data_list = []
42
- for i, item in enumerate(dataset):
43
- data_list.append(item)
44
- if i < 5:
45
- print(f"📋 Exemple {i}: {list(item.keys())}")
46
- self.df = pd.DataFrame(data_list)
47
- print(f"✅ Conversion manuelle réussie: {len(self.df)} lignes")
48
- except Exception as e:
49
- print(f"❌ Erreur lors du chargement depuis Hugging Face: {str(e)}")
50
- print(f" Type d'erreur: {type(e).__name__}")
51
- # 2) Fallback: récupérer directement les fichiers du repo
52
- fallback_msg = self._fallback_load_from_repo_files()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  if self.df is None:
54
- return f" Erreur lors du chargement du dataset : {str(e)} | Fallback: {fallback_msg}"
 
55
 
56
- # Si on n'a toujours pas de dataframe, arrêter
57
- if self.df is None:
58
- return MESSAGES["no_data"]
59
 
60
- print(f"📊 Données chargées: {len(self.df)} lignes")
61
- print(f"📊 Colonnes disponibles: {list(self.df.columns)}")
62
-
63
- # Nettoyage et validation
64
- return self._clean_and_validate_data()
 
 
 
65
 
66
  def _clean_and_validate_data(self):
67
  """Nettoie et valide les données chargées"""
 
15
  self.df = None
16
 
17
  def load_data(self):
18
+ """Charge les données du dataset Hugging Face avec gestion d'erreur robuste"""
 
 
 
 
 
 
 
19
  try:
20
+ print(MESSAGES["loading"])
21
+ print(f"📋 Dataset ID: {DATASET_ID}")
22
+ print(f"📋 Token disponible: {'Oui' if HF_TOKEN else 'Non'}")
 
 
 
 
23
 
24
+ self.df = None
25
+
26
+ # 1) Tentative de chargement direct via datasets.load_dataset
27
  try:
28
+ print("🔄 Tentative de chargement direct...")
29
+ dataset = load_dataset(
30
+ DATASET_ID,
31
+ split="train",
32
+ token=HF_TOKEN,
33
+ trust_remote_code=True,
34
+ )
35
+ print(f"📊 Dataset chargé: {len(dataset)} exemples")
36
+
37
+ try:
38
+ self.df = dataset.to_pandas()
39
+ print("✅ Conversion to_pandas() réussie")
40
+ except Exception as pandas_error:
41
+ print(f"❌ Erreur to_pandas(): {pandas_error}")
42
+ print("🔄 Tentative de conversion manuelle...")
43
+
44
+ try:
45
+ data_list = []
46
+ max_examples = min(len(dataset), 1000) # Limiter pour éviter les problèmes de mémoire
47
+
48
+ for i, item in enumerate(dataset):
49
+ if i >= max_examples:
50
+ break
51
+ data_list.append(item)
52
+ if i < 5:
53
+ print(f"📋 Exemple {i}: {list(item.keys())}")
54
+
55
+ self.df = pd.DataFrame(data_list)
56
+ print(f"✅ Conversion manuelle réussie: {len(self.df)} lignes")
57
+ except Exception as manual_error:
58
+ print(f"❌ Erreur lors de la conversion manuelle: {manual_error}")
59
+ self.df = None
60
+
61
+ except Exception as e:
62
+ print(f"❌ Erreur lors du chargement depuis Hugging Face: {str(e)}")
63
+ print(f"❌ Type d'erreur: {type(e).__name__}")
64
+
65
+ # 2) Fallback: récupérer directement les fichiers du repo
66
+ try:
67
+ fallback_msg = self._fallback_load_from_repo_files()
68
+ if self.df is None and fallback_msg:
69
+ return f"❌ Erreur lors du chargement du dataset : {str(e)} | Fallback: {fallback_msg}"
70
+ except Exception as fallback_error:
71
+ print(f"❌ Erreur dans le fallback: {fallback_error}")
72
+ # Continue vers le chargement local
73
+
74
+ # Si on n'a toujours pas de dataframe, arrêter
75
  if self.df is None:
76
+ print("⚠️ Aucune méthode de chargement n'a fonctionné")
77
+ return MESSAGES["no_data"]
78
 
79
+ print(f"📊 Données chargées: {len(self.df)} lignes")
80
+ print(f"📊 Colonnes disponibles: {list(self.df.columns)}")
 
81
 
82
+ # Nettoyage et validation
83
+ return self._clean_and_validate_data()
84
+
85
+ except Exception as e:
86
+ print(f"❌ Erreur critique dans load_data: {e}")
87
+ import traceback
88
+ traceback.print_exc()
89
+ return f"❌ Erreur critique lors du chargement: {str(e)}"
90
 
91
  def _clean_and_validate_data(self):
92
  """Nettoie et valide les données chargées"""
interface.py CHANGED
@@ -22,43 +22,126 @@ class AgricultureInterface:
22
  self._initialize_data()
23
 
24
  def _initialize_data(self):
25
- """Initialise les données au démarrage"""
26
- self.data_loader.load_data()
27
- if self.data_loader.has_data():
28
- self.analyzer.set_data(self.data_loader.get_data())
29
- self.analyzer.analyze_data()
30
- self.visualizer.set_data(
31
- self.data_loader.get_data(),
32
- self.analyzer.get_risk_analysis()
33
- )
 
 
 
 
 
 
 
 
34
 
35
  def refresh_data(self):
36
- """Rafraîchit toutes les données"""
37
- self.data_loader.load_data()
38
- if self.data_loader.has_data():
39
- self.analyzer.set_data(self.data_loader.get_data())
40
- self.analyzer.analyze_data()
41
- self.visualizer.set_data(
42
- self.data_loader.get_data(),
43
- self.analyzer.get_risk_analysis()
44
- )
45
- return (
46
- self.analyzer.get_summary_stats(),
47
- self.visualizer.create_culture_analysis(),
48
- self.visualizer.create_risk_distribution(),
49
- self.visualizer.create_risk_visualization(),
50
- self.analyzer.get_low_risk_recommendations()
51
- )
52
- else:
53
- # Retourner des valeurs par défaut si pas de données
54
- empty_fig = self.visualizer.create_culture_analysis() # Créera un graphique vide
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  return (
56
- "❌ Aucune donnée disponible",
57
  empty_fig,
58
  empty_fig,
59
  empty_fig,
60
- "❌ Aucune recommandation disponible"
61
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  def create_interface(self):
64
  """Crée l'interface Gradio"""
@@ -103,37 +186,46 @@ class AgricultureInterface:
103
  return demo
104
 
105
  def _create_overview_tab(self):
106
- """Crée l'onglet de vue d'ensemble"""
107
- gr.Markdown("## Statistiques générales des données agricoles")
108
-
109
- self.stats_output = gr.Markdown(self.analyzer.get_summary_stats())
110
-
111
- with gr.Row():
112
- self.culture_plot = gr.Plot(self.visualizer.create_culture_analysis())
113
- self.risk_dist_plot = gr.Plot(self.visualizer.create_risk_distribution())
 
 
 
 
114
 
115
  def _create_risk_analysis_tab(self):
116
- """Crée l'onglet d'analyse des risques"""
117
- gr.Markdown("## Cartographie des risques adventices par parcelle")
118
-
119
- self.risk_plot = gr.Plot(self.visualizer.create_risk_visualization())
120
-
121
- gr.Markdown("""
122
- **Interprétation du graphique**:
123
- - **Axe X**: Surface de la parcelle (hectares)
124
- - **Axe Y**: IFT Herbicide approximatif
125
- - **Couleur**: Niveau de risque adventice
126
- - **Taille**: Nombre d'herbicides utilisés
127
-
128
- Les parcelles vertes (risque faible) sont idéales pour les cultures sensibles.
129
- """)
 
 
 
 
130
 
131
  def _create_recommendations_tab(self):
132
- """Crée l'onglet des recommandations"""
133
- self.reco_output = gr.Markdown(self.analyzer.get_low_risk_recommendations())
134
-
135
- gr.Markdown("""
136
- ## 💡 Conseils pour la gestion des adventices
 
137
 
138
  ### Parcelles à Très Faible Risque (Vertes)
139
  - ✅ **Idéales pour pois et haricot**
@@ -155,6 +247,9 @@ class AgricultureInterface:
155
  - **Techniques mécaniques**: Hersage, binage
156
  - **Biostimulants**: Renforcement naturel des cultures
157
  """)
 
 
 
158
 
159
  def _create_about_tab(self):
160
  """Crée l'onglet à propos"""
 
22
  self._initialize_data()
23
 
24
  def _initialize_data(self):
25
+ """Initialise les données au démarrage avec gestion d'erreur"""
26
+ try:
27
+ print("🔄 Initialisation des données...")
28
+ self.data_loader.load_data()
29
+ if self.data_loader.has_data():
30
+ self.analyzer.set_data(self.data_loader.get_data())
31
+ self.analyzer.analyze_data()
32
+ self.visualizer.set_data(
33
+ self.data_loader.get_data(),
34
+ self.analyzer.get_risk_analysis()
35
+ )
36
+ print("✅ Initialisation réussie")
37
+ else:
38
+ print("⚠️ Aucune donnée disponible au démarrage")
39
+ except Exception as e:
40
+ print(f"❌ Erreur lors de l'initialisation: {e}")
41
+ # L'application peut continuer même si l'initialisation échoue
42
 
43
  def refresh_data(self):
44
+ """Rafraîchit toutes les données avec gestion d'erreur robuste"""
45
+ try:
46
+ print("🔄 Rafraîchissement des données...")
47
+
48
+ # Chargement des données
49
+ try:
50
+ self.data_loader.load_data()
51
+ except Exception as e:
52
+ print(f"❌ Erreur lors du chargement: {e}")
53
+ return self._get_error_outputs("Erreur lors du chargement des données")
54
+
55
+ if self.data_loader.has_data():
56
+ try:
57
+ # Analyse des données
58
+ self.analyzer.set_data(self.data_loader.get_data())
59
+ self.analyzer.analyze_data()
60
+ self.visualizer.set_data(
61
+ self.data_loader.get_data(),
62
+ self.analyzer.get_risk_analysis()
63
+ )
64
+
65
+ # Génération des outputs avec gestion d'erreur individuelle
66
+ return (
67
+ self._safe_get_summary_stats(),
68
+ self._safe_create_culture_analysis(),
69
+ self._safe_create_risk_distribution(),
70
+ self._safe_create_risk_visualization(),
71
+ self._safe_get_recommendations()
72
+ )
73
+
74
+ except Exception as e:
75
+ print(f"❌ Erreur lors de l'analyse: {e}")
76
+ return self._get_error_outputs(f"Erreur lors de l'analyse: {str(e)[:100]}...")
77
+ else:
78
+ print("⚠️ Aucune donnée disponible après chargement")
79
+ return self._get_error_outputs("Aucune donnée disponible")
80
+
81
+ except Exception as e:
82
+ print(f"❌ Erreur critique dans refresh_data: {e}")
83
+ return self._get_error_outputs("Erreur critique lors du rafraîchissement")
84
+
85
+ def _get_error_outputs(self, error_message):
86
+ """Retourne des outputs d'erreur standardisés"""
87
+ try:
88
+ empty_fig = self.visualizer._create_error_plot("❌ Erreur", error_message)
89
  return (
90
+ f"❌ {error_message}",
91
  empty_fig,
92
  empty_fig,
93
  empty_fig,
94
+ f"❌ {error_message}"
95
  )
96
+ except:
97
+ # Fallback ultime si même la création d'erreur échoue
98
+ return (
99
+ "❌ Erreur critique",
100
+ None,
101
+ None,
102
+ None,
103
+ "❌ Erreur critique"
104
+ )
105
+
106
+ def _safe_get_summary_stats(self):
107
+ """Récupère les statistiques avec gestion d'erreur"""
108
+ try:
109
+ return self.analyzer.get_summary_stats()
110
+ except Exception as e:
111
+ print(f"❌ Erreur dans get_summary_stats: {e}")
112
+ return "❌ Erreur lors de la génération des statistiques"
113
+
114
+ def _safe_create_culture_analysis(self):
115
+ """Crée l'analyse des cultures avec gestion d'erreur"""
116
+ try:
117
+ return self.visualizer.create_culture_analysis()
118
+ except Exception as e:
119
+ print(f"❌ Erreur dans create_culture_analysis: {e}")
120
+ return self.visualizer._create_error_plot("❌ Erreur", "Impossible de créer l'analyse des cultures")
121
+
122
+ def _safe_create_risk_distribution(self):
123
+ """Crée la distribution des risques avec gestion d'erreur"""
124
+ try:
125
+ return self.visualizer.create_risk_distribution()
126
+ except Exception as e:
127
+ print(f"❌ Erreur dans create_risk_distribution: {e}")
128
+ return self.visualizer._create_error_plot("❌ Erreur", "Impossible de créer la distribution des risques")
129
+
130
+ def _safe_create_risk_visualization(self):
131
+ """Crée la visualisation des risques avec gestion d'erreur"""
132
+ try:
133
+ return self.visualizer.create_risk_visualization()
134
+ except Exception as e:
135
+ print(f"❌ Erreur dans create_risk_visualization: {e}")
136
+ return self.visualizer._create_error_plot("❌ Erreur", "Impossible de créer la visualisation des risques")
137
+
138
+ def _safe_get_recommendations(self):
139
+ """Récupère les recommandations avec gestion d'erreur"""
140
+ try:
141
+ return self.analyzer.get_low_risk_recommendations()
142
+ except Exception as e:
143
+ print(f"❌ Erreur dans get_low_risk_recommendations: {e}")
144
+ return "❌ Erreur lors de la génération des recommandations"
145
 
146
  def create_interface(self):
147
  """Crée l'interface Gradio"""
 
186
  return demo
187
 
188
  def _create_overview_tab(self):
189
+ """Crée l'onglet de vue d'ensemble avec gestion d'erreur"""
190
+ try:
191
+ gr.Markdown("## Statistiques générales des données agricoles")
192
+
193
+ self.stats_output = gr.Markdown(self._safe_get_summary_stats())
194
+
195
+ with gr.Row():
196
+ self.culture_plot = gr.Plot(self._safe_create_culture_analysis())
197
+ self.risk_dist_plot = gr.Plot(self._safe_create_risk_distribution())
198
+ except Exception as e:
199
+ print(f"❌ Erreur lors de la création de l'onglet vue d'ensemble: {e}")
200
+ gr.Markdown("❌ Erreur lors de la création de l'interface")
201
 
202
  def _create_risk_analysis_tab(self):
203
+ """Crée l'onglet d'analyse des risques avec gestion d'erreur"""
204
+ try:
205
+ gr.Markdown("## Cartographie des risques adventices par parcelle")
206
+
207
+ self.risk_plot = gr.Plot(self._safe_create_risk_visualization())
208
+
209
+ gr.Markdown("""
210
+ **Interprétation du graphique**:
211
+ - **Axe X**: Surface de la parcelle (hectares)
212
+ - **Axe Y**: IFT Herbicide approximatif
213
+ - **Couleur**: Niveau de risque adventice
214
+ - **Taille**: Nombre d'herbicides utilisés
215
+
216
+ Les parcelles vertes (risque faible) sont idéales pour les cultures sensibles.
217
+ """)
218
+ except Exception as e:
219
+ print(f"❌ Erreur lors de la création de l'onglet analyse des risques: {e}")
220
+ gr.Markdown("❌ Erreur lors de la création de l'interface d'analyse des risques")
221
 
222
  def _create_recommendations_tab(self):
223
+ """Crée l'onglet des recommandations avec gestion d'erreur"""
224
+ try:
225
+ self.reco_output = gr.Markdown(self._safe_get_recommendations())
226
+
227
+ gr.Markdown("""
228
+ ## 💡 Conseils pour la gestion des adventices
229
 
230
  ### Parcelles à Très Faible Risque (Vertes)
231
  - ✅ **Idéales pour pois et haricot**
 
247
  - **Techniques mécaniques**: Hersage, binage
248
  - **Biostimulants**: Renforcement naturel des cultures
249
  """)
250
+ except Exception as e:
251
+ print(f"❌ Erreur lors de la création de l'onglet recommandations: {e}")
252
+ gr.Markdown("❌ Erreur lors de la création de l'interface de recommandations")
253
 
254
  def _create_about_tab(self):
255
  """Crée l'onglet à propos"""
main.py CHANGED
@@ -1,10 +1,12 @@
1
  """
2
  Point d'entrée principal de l'application d'analyse des adventices agricoles
 
3
  """
4
  import warnings
5
  import matplotlib.pyplot as plt
6
  import seaborn as sns
7
- from interface import AgricultureInterface
 
8
 
9
  # Suppression des warnings
10
  warnings.filterwarnings('ignore')
@@ -14,13 +16,77 @@ plt.style.use('default')
14
  sns.set_palette("husl")
15
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  def main():
18
- """Fonction principale qui lance l'application"""
19
- print("🌾 Démarrage de l'application d'analyse des adventices agricoles...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- # Création et lancement de l'interface
22
- app = AgricultureInterface()
23
- app.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
 
26
  if __name__ == "__main__":
 
1
  """
2
  Point d'entrée principal de l'application d'analyse des adventices agricoles
3
+ Avec détection automatique des problèmes et fallback
4
  """
5
  import warnings
6
  import matplotlib.pyplot as plt
7
  import seaborn as sns
8
+ import sys
9
+ import os
10
 
11
  # Suppression des warnings
12
  warnings.filterwarnings('ignore')
 
16
  sns.set_palette("husl")
17
 
18
 
19
+ def try_gradio_interface():
20
+ """Essaie de lancer l'interface Gradio"""
21
+ try:
22
+ print("🔄 Tentative de lancement de l'interface Gradio...")
23
+ from interface import AgricultureInterface
24
+
25
+ app = AgricultureInterface()
26
+ app.launch()
27
+ return True
28
+
29
+ except ImportError as e:
30
+ print(f"❌ Erreur d'import: {e}")
31
+ if "pyaudioop" in str(e) or "audioop" in str(e):
32
+ print("🔍 Problème détecté: Dépendance audio manquante pour Gradio")
33
+ return False
34
+
35
+ except Exception as e:
36
+ print(f"❌ Erreur lors du lancement de Gradio: {e}")
37
+ return False
38
+
39
+
40
+ def run_fallback():
41
+ """Lance le mode fallback sans Gradio"""
42
+ try:
43
+ print("🔄 Basculement vers le mode fallback...")
44
+ from main_fallback import run_analysis_without_ui
45
+
46
+ return run_analysis_without_ui()
47
+
48
+ except Exception as e:
49
+ print(f"❌ Erreur dans le mode fallback: {e}")
50
+ return False
51
+
52
+
53
  def main():
54
+ """Fonction principale avec gestion d'erreur et fallback automatique"""
55
+ print("🌾 Application d'Analyse des Adventices Agricoles")
56
+ print("=" * 60)
57
+
58
+ # Tentative 1: Interface Gradio complète
59
+ print("\n🎯 Tentative 1: Interface web avec Gradio")
60
+
61
+ try:
62
+ if try_gradio_interface():
63
+ return # Succès, on s'arrête ici
64
+ except KeyboardInterrupt:
65
+ print("\n⏹️ Arrêt demandé par l'utilisateur")
66
+ return
67
+ except Exception as e:
68
+ print(f"❌ Échec critique de l'interface Gradio: {e}")
69
+
70
+ # Tentative 2: Mode fallback console
71
+ print("\n🎯 Tentative 2: Mode console (fallback)")
72
 
73
+ try:
74
+ if run_fallback():
75
+ print("\n✅ Mode fallback exécuté avec succès")
76
+ print("\n💡 Pour utiliser l'interface web, résolvez les problèmes de dépendances:")
77
+ print(" - Installez les dépendances audio manquantes")
78
+ print(" - Ou utilisez une version compatible de Gradio")
79
+ else:
80
+ print("\n❌ Échec du mode fallback")
81
+ exit(1)
82
+
83
+ except KeyboardInterrupt:
84
+ print("\n⏹️ Arrêt demandé par l'utilisateur")
85
+ return
86
+ except Exception as e:
87
+ print(f"\n❌ Erreur critique dans le mode fallback: {e}")
88
+ print("🆘 Aucun mode disponible - vérifiez vos dépendances")
89
+ exit(1)
90
 
91
 
92
  if __name__ == "__main__":
main_fallback.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Version alternative de main.py qui fonctionne sans Gradio
3
+ Pour les cas où les dépendances Gradio posent problème
4
+ """
5
+ import warnings
6
+ import matplotlib.pyplot as plt
7
+ import seaborn as sns
8
+ from data_loader import DataLoader
9
+ from analyzer import AgricultureAnalyzer
10
+ from visualizations import AgricultureVisualizer
11
+
12
+ # Suppression des warnings
13
+ warnings.filterwarnings('ignore')
14
+
15
+ # Configuration des graphiques
16
+ plt.style.use('default')
17
+ sns.set_palette("husl")
18
+
19
+
20
+ def run_analysis_without_ui():
21
+ """Exécute l'analyse sans interface utilisateur"""
22
+ print("🌾 Analyse des Adventices Agricoles - Mode Console")
23
+ print("=" * 60)
24
+
25
+ try:
26
+ # Initialisation des composants
27
+ print("🔄 Initialisation des composants...")
28
+ data_loader = DataLoader()
29
+ analyzer = AgricultureAnalyzer()
30
+ visualizer = AgricultureVisualizer()
31
+
32
+ # Chargement des données
33
+ print("\n📊 Chargement des données...")
34
+ result = data_loader.load_data()
35
+ if isinstance(result, str) and "❌" in result:
36
+ print(f"Erreur: {result}")
37
+ return False
38
+
39
+ if not data_loader.has_data():
40
+ print("❌ Aucune donnée disponible")
41
+ return False
42
+
43
+ data = data_loader.get_data()
44
+ print(f"✅ {len(data)} enregistrements chargés")
45
+
46
+ # Analyse des données
47
+ print("\n🔬 Analyse des données...")
48
+ analyzer.set_data(data)
49
+ general_stats, herbicide_stats = analyzer.analyze_data()
50
+
51
+ if general_stats:
52
+ print("✅ Analyse générale terminée")
53
+ print(f" - Parcelles: {general_stats['total_parcelles']}")
54
+ print(f" - Interventions: {general_stats['total_interventions']}")
55
+ print(f" - Surface totale: {general_stats['surface_totale']:.2f} ha")
56
+
57
+ if herbicide_stats:
58
+ print("✅ Analyse herbicides terminée")
59
+ print(f" - Interventions herbicides: {herbicide_stats['nb_interventions_herbicides']}")
60
+ print(f" - Pourcentage: {herbicide_stats['pourcentage_herbicides']:.1f}%")
61
+
62
+ # Génération des statistiques
63
+ print("\n📋 Génération des statistiques...")
64
+ stats = analyzer.get_summary_stats()
65
+ print("✅ Statistiques générées")
66
+
67
+ # Génération des recommandations
68
+ print("\n🎯 Génération des recommandations...")
69
+ recommendations = analyzer.get_low_risk_recommendations()
70
+ print("✅ Recommandations générées")
71
+
72
+ # Sauvegarde des résultats
73
+ print("\n💾 Sauvegarde des résultats...")
74
+ try:
75
+ # Sauvegarde des statistiques
76
+ with open('results/stats_console.md', 'w', encoding='utf-8') as f:
77
+ f.write("# Analyse des Adventices Agricoles\n\n")
78
+ f.write(stats)
79
+ f.write("\n\n")
80
+ f.write(recommendations)
81
+
82
+ # Sauvegarde de l'analyse des risques si disponible
83
+ risk_analysis = analyzer.get_risk_analysis()
84
+ if risk_analysis is not None and len(risk_analysis) > 0:
85
+ risk_analysis.to_csv('results/risk_analysis_console.csv')
86
+ print("✅ Analyse des risques sauvegardée (CSV)")
87
+
88
+ print("✅ Résultats sauvegardés dans results/")
89
+
90
+ except Exception as e:
91
+ print(f"⚠️ Erreur lors de la sauvegarde: {e}")
92
+
93
+ # Tentative de génération des graphiques
94
+ print("\n📈 Tentative de génération des graphiques...")
95
+ try:
96
+ visualizer.set_data(data, analyzer.get_risk_analysis())
97
+
98
+ # Les graphiques seront créés mais ne pourront pas être affichés en mode console
99
+ print("⚠️ Les graphiques sont générés mais ne peuvent pas être affichés en mode console")
100
+ print("💡 Utilisez main.py avec Gradio pour voir les visualisations interactives")
101
+
102
+ except Exception as e:
103
+ print(f"⚠️ Impossible de générer les graphiques: {e}")
104
+
105
+ print("\n🎉 Analyse terminée avec succès!")
106
+ print("\n📁 Fichiers générés:")
107
+ print(" - results/stats_console.md : Statistiques et recommandations")
108
+ print(" - results/risk_analysis_console.csv : Analyse détaillée des risques")
109
+
110
+ return True
111
+
112
+ except Exception as e:
113
+ print(f"❌ Erreur critique: {e}")
114
+ import traceback
115
+ traceback.print_exc()
116
+ return False
117
+
118
+
119
+ def main():
120
+ """Fonction principale en mode fallback"""
121
+ print("🔧 Mode Fallback - Analyse sans interface Gradio")
122
+
123
+ try:
124
+ # Tentative d'import de Gradio pour vérifier la disponibilité
125
+ try:
126
+ import gradio as gr
127
+ print("✅ Gradio disponible - vous pouvez utiliser main.py")
128
+ print("⚠️ Utilisation du mode fallback forcé")
129
+ except ImportError as e:
130
+ print(f"⚠️ Gradio non disponible: {e}")
131
+ print("🔄 Utilisation du mode console")
132
+ except Exception as e:
133
+ print(f"⚠️ Problème avec Gradio: {e}")
134
+ print("🔄 Utilisation du mode console")
135
+
136
+ # Exécution de l'analyse
137
+ success = run_analysis_without_ui()
138
+
139
+ if success:
140
+ print("\n✅ Mode fallback exécuté avec succès")
141
+ else:
142
+ print("\n❌ Échec du mode fallback")
143
+ exit(1)
144
+
145
+
146
+ if __name__ == "__main__":
147
+ main()
visualizations.py CHANGED
@@ -19,162 +19,334 @@ class AgricultureVisualizer:
19
  if risk_analysis is not None:
20
  self.risk_analysis = risk_analysis
21
 
22
- def create_risk_visualization(self):
23
- """Crée la visualisation des risques"""
24
- if self.risk_analysis is None or len(self.risk_analysis) == 0:
25
- # Créer un graphique vide avec message d'erreur
26
- fig = px.scatter(title="❌ Aucune donnée d'analyse des risques disponible")
27
  fig.add_annotation(
28
- text="Veuillez charger les données d'abord",
29
- xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False
 
 
 
 
 
 
 
30
  )
31
  return fig
32
-
33
- risk_df = self.risk_analysis.reset_index()
34
-
35
- # Vérifier quelles colonnes sont disponibles pour hover_data
36
- available_hover_cols = []
37
- for col in ['nomparc', 'libelleusag']:
38
- if col in risk_df.columns:
39
- available_hover_cols.append(col)
40
-
41
- fig = px.scatter(
42
- risk_df,
43
- x='surfparc',
44
- y='IFT_herbicide_approx',
45
- color='Risque_adventice',
46
- size='Nb_herbicides',
47
- hover_data=available_hover_cols if available_hover_cols else None,
48
- color_discrete_map=RISK_COLORS,
49
- title="🎯 Analyse du Risque Adventice par Parcelle",
50
- labels={
51
- 'surfparc': 'Surface de la parcelle (ha)',
52
- 'IFT_herbicide_approx': 'IFT Herbicide (approximatif)',
53
- 'Risque_adventice': 'Niveau de risque'
54
- }
55
- )
56
-
57
- fig.update_layout(
58
- width=PLOT_CONFIG["width"],
59
- height=PLOT_CONFIG["height"],
60
- title_font_size=PLOT_CONFIG["title_font_size"]
61
- )
62
- return fig
63
 
64
- def create_culture_analysis(self):
65
- """Analyse par type de culture"""
66
- if self.df is None or len(self.df) == 0:
67
- # Créer un graphique vide avec message d'erreur
68
- fig = px.pie(title="❌ Aucune donnée disponible")
69
- fig.add_annotation(
70
- text="Veuillez charger les données d'abord",
71
- xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  )
73
- return fig
74
-
75
- if 'libelleusag' not in self.df.columns:
76
- fig = px.pie(title="❌ Colonne 'libelleusag' non disponible")
77
- fig.add_annotation(
78
- text="Les données de culture ne sont pas disponibles",
79
- xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  )
81
- return fig
82
-
83
- culture_counts = self.df['libelleusag'].value_counts()
84
-
85
- fig = px.pie(
86
- values=culture_counts.values,
87
- names=culture_counts.index,
88
- title="🌱 Répartition des Cultures"
89
- )
90
-
91
- fig.update_layout(width=700, height=500)
92
- return fig
93
 
94
  def create_risk_distribution(self):
95
- """Distribution des niveaux de risque"""
96
- if self.risk_analysis is None or len(self.risk_analysis) == 0:
97
- # Créer un graphique vide avec message d'erreur
98
- fig = px.bar(title="❌ Aucune analyse des risques disponible")
99
- fig.add_annotation(
100
- text="Veuillez charger les données d'abord",
101
- xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  )
103
- return fig
104
-
105
- risk_counts = self.risk_analysis['Risque_adventice'].value_counts()
106
-
107
- fig = px.bar(
108
- x=risk_counts.index,
109
- y=risk_counts.values,
110
- color=risk_counts.index,
111
- color_discrete_map=RISK_COLORS,
112
- title="📊 Distribution des Niveaux de Risque Adventice",
113
- labels={'x': 'Niveau de risque', 'y': 'Nombre de parcelles'}
114
- )
115
-
116
- fig.update_layout(width=700, height=500, showlegend=False)
117
- return fig
118
 
119
  def create_herbicide_timeline(self):
120
- """Crée un graphique de l'évolution temporelle des herbicides"""
121
- if self.df is None or len(self.df) == 0:
122
- fig = px.line(title="❌ Aucune donnée disponible")
123
- fig.add_annotation(
124
- text="Veuillez charger les données d'abord",
125
- xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False
126
- )
127
- return fig
128
-
129
- if 'millesime' not in self.df.columns or 'familleprod' not in self.df.columns:
130
- fig = px.line(title="❌ Colonnes nécessaires non disponibles")
131
- fig.add_annotation(
132
- text="Les données temporelles ne sont pas disponibles",
133
- xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  )
135
- return fig
136
-
137
- # Filtrer les herbicides et grouper par année
138
- herbicides_df = self.df[self.df['familleprod'] == 'Herbicides']
139
- if len(herbicides_df) == 0:
140
- fig = px.line(title="❌ Aucune donnée d'herbicide disponible")
141
- return fig
142
-
143
- yearly_herbicides = herbicides_df.groupby('millesime').agg({
144
- 'numparcell': 'nunique',
145
- 'quantitetot': 'sum'
146
- }).reset_index()
147
-
148
- fig = px.line(
149
- yearly_herbicides,
150
- x='millesime',
151
- y='quantitetot',
152
- title="📈 Évolution de l'Usage des Herbicides par Année",
153
- labels={
154
- 'millesime': 'Année',
155
- 'quantitetot': 'Quantité totale d\'herbicides'
156
- }
157
- )
158
-
159
- fig.update_layout(width=700, height=400)
160
- return fig
161
 
162
  def create_surface_analysis(self):
163
- """Analyse de la distribution des surfaces"""
164
- if self.df is None or len(self.df) == 0:
165
- fig = px.histogram(title="❌ Aucune donnée disponible")
166
- return fig
167
-
168
- fig = px.histogram(
169
- self.df,
170
- x='surfparc',
171
- nbins=20,
172
- title="📏 Distribution des Surfaces de Parcelles",
173
- labels={
174
- 'surfparc': 'Surface (ha)',
175
- 'count': 'Nombre de parcelles'
176
- }
177
- )
178
-
179
- fig.update_layout(width=700, height=400)
180
- return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  if risk_analysis is not None:
20
  self.risk_analysis = risk_analysis
21
 
22
+ def _create_error_plot(self, title, message):
23
+ """Crée un graphique d'erreur standardisé"""
24
+ try:
25
+ fig = px.scatter(title=title)
 
26
  fig.add_annotation(
27
+ text=message,
28
+ xref="paper", yref="paper",
29
+ x=0.5, y=0.5,
30
+ showarrow=False,
31
+ font=dict(size=14, color="red")
32
+ )
33
+ fig.update_layout(
34
+ width=PLOT_CONFIG.get("width", 700),
35
+ height=PLOT_CONFIG.get("height", 400)
36
  )
37
  return fig
38
+ except Exception as e:
39
+ print(f"❌ Erreur lors de la création du graphique d'erreur: {e}")
40
+ # Retourner un graphique minimal en cas d'échec total
41
+ return go.Figure().add_annotation(text="Erreur critique", x=0.5, y=0.5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ def create_risk_visualization(self):
44
+ """Crée la visualisation des risques avec gestion d'erreur robuste"""
45
+ try:
46
+ if self.risk_analysis is None or len(self.risk_analysis) == 0:
47
+ return self._create_error_plot(
48
+ "❌ Aucune donnée d'analyse des risques disponible",
49
+ "Veuillez charger et analyser les données d'abord"
50
+ )
51
+
52
+ try:
53
+ risk_df = self.risk_analysis.reset_index()
54
+
55
+ # Vérifier les colonnes requises
56
+ required_cols = ['surfparc', 'IFT_herbicide_approx', 'Risque_adventice', 'Nb_herbicides']
57
+ missing_cols = [col for col in required_cols if col not in risk_df.columns]
58
+
59
+ if missing_cols:
60
+ return self._create_error_plot(
61
+ f"❌ Colonnes manquantes: {missing_cols}",
62
+ "Les données ne contiennent pas toutes les colonnes nécessaires"
63
+ )
64
+
65
+ # Vérifier quelles colonnes sont disponibles pour hover_data
66
+ available_hover_cols = []
67
+ for col in ['nomparc', 'libelleusag']:
68
+ if col in risk_df.columns:
69
+ available_hover_cols.append(col)
70
+
71
+ # Nettoyer les données pour éviter les erreurs de plotting
72
+ risk_df = risk_df.dropna(subset=required_cols)
73
+
74
+ if len(risk_df) == 0:
75
+ return self._create_error_plot(
76
+ "❌ Aucune donnée valide après nettoyage",
77
+ "Toutes les données contiennent des valeurs manquantes"
78
+ )
79
+
80
+ fig = px.scatter(
81
+ risk_df,
82
+ x='surfparc',
83
+ y='IFT_herbicide_approx',
84
+ color='Risque_adventice',
85
+ size='Nb_herbicides',
86
+ hover_data=available_hover_cols if available_hover_cols else None,
87
+ color_discrete_map=RISK_COLORS,
88
+ title="🎯 Analyse du Risque Adventice par Parcelle",
89
+ labels={
90
+ 'surfparc': 'Surface de la parcelle (ha)',
91
+ 'IFT_herbicide_approx': 'IFT Herbicide (approximatif)',
92
+ 'Risque_adventice': 'Niveau de risque'
93
+ }
94
+ )
95
+
96
+ fig.update_layout(
97
+ width=PLOT_CONFIG["width"],
98
+ height=PLOT_CONFIG["height"],
99
+ title_font_size=PLOT_CONFIG["title_font_size"]
100
+ )
101
+ return fig
102
+
103
+ except Exception as e:
104
+ print(f"❌ Erreur lors de la création du graphique de risque: {e}")
105
+ return self._create_error_plot(
106
+ "❌ Erreur lors de la création du graphique",
107
+ f"Erreur technique: {str(e)[:100]}..."
108
+ )
109
+
110
+ except Exception as e:
111
+ print(f"❌ Erreur critique dans create_risk_visualization: {e}")
112
+ return self._create_error_plot(
113
+ "❌ Erreur critique",
114
+ "Impossible de créer la visualisation des risques"
115
  )
116
+
117
+ def create_culture_analysis(self):
118
+ """Analyse par type de culture avec gestion d'erreur robuste"""
119
+ try:
120
+ if self.df is None or len(self.df) == 0:
121
+ return self._create_error_plot(
122
+ " Aucune donnée disponible",
123
+ "Veuillez charger les données d'abord"
124
+ )
125
+
126
+ if 'libelleusag' not in self.df.columns:
127
+ return self._create_error_plot(
128
+ "❌ Colonne 'libelleusag' non disponible",
129
+ "Les données de culture ne sont pas disponibles"
130
+ )
131
+
132
+ try:
133
+ # Nettoyer les données de culture
134
+ culture_data = self.df['libelleusag'].dropna()
135
+
136
+ if len(culture_data) == 0:
137
+ return self._create_error_plot(
138
+ "❌ Aucune donnée de culture valide",
139
+ "Toutes les valeurs de culture sont manquantes"
140
+ )
141
+
142
+ culture_counts = culture_data.value_counts()
143
+
144
+ if len(culture_counts) == 0:
145
+ return self._create_error_plot(
146
+ "❌ Aucune culture détectée",
147
+ "Aucune donnée de culture trouvée après nettoyage"
148
+ )
149
+
150
+ fig = px.pie(
151
+ values=culture_counts.values,
152
+ names=culture_counts.index,
153
+ title="🌱 Répartition des Cultures"
154
+ )
155
+
156
+ fig.update_layout(width=700, height=500)
157
+ return fig
158
+
159
+ except Exception as e:
160
+ print(f"❌ Erreur lors de la création du graphique de culture: {e}")
161
+ return self._create_error_plot(
162
+ "❌ Erreur lors de la création du graphique",
163
+ f"Erreur technique: {str(e)[:100]}..."
164
+ )
165
+
166
+ except Exception as e:
167
+ print(f"❌ Erreur critique dans create_culture_analysis: {e}")
168
+ return self._create_error_plot(
169
+ "❌ Erreur critique",
170
+ "Impossible de créer l'analyse des cultures"
171
  )
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
  def create_risk_distribution(self):
174
+ """Distribution des niveaux de risque avec gestion d'erreur robuste"""
175
+ try:
176
+ if self.risk_analysis is None or len(self.risk_analysis) == 0:
177
+ return self._create_error_plot(
178
+ "❌ Aucune analyse des risques disponible",
179
+ "Veuillez charger et analyser les données d'abord"
180
+ )
181
+
182
+ if 'Risque_adventice' not in self.risk_analysis.columns:
183
+ return self._create_error_plot(
184
+ "❌ Colonne 'Risque_adventice' manquante",
185
+ "L'analyse des risques est incomplète"
186
+ )
187
+
188
+ try:
189
+ # Nettoyer les données de risque
190
+ risk_data = self.risk_analysis['Risque_adventice'].dropna()
191
+
192
+ if len(risk_data) == 0:
193
+ return self._create_error_plot(
194
+ "❌ Aucune donnée de risque valide",
195
+ "Toutes les valeurs de risque sont manquantes"
196
+ )
197
+
198
+ risk_counts = risk_data.value_counts()
199
+
200
+ if len(risk_counts) == 0:
201
+ return self._create_error_plot(
202
+ "❌ Aucun niveau de risque détecté",
203
+ "Aucune donnée de risque trouvée après nettoyage"
204
+ )
205
+
206
+ fig = px.bar(
207
+ x=risk_counts.index,
208
+ y=risk_counts.values,
209
+ color=risk_counts.index,
210
+ color_discrete_map=RISK_COLORS,
211
+ title="📊 Distribution des Niveaux de Risque Adventice",
212
+ labels={'x': 'Niveau de risque', 'y': 'Nombre de parcelles'}
213
+ )
214
+
215
+ fig.update_layout(width=700, height=500, showlegend=False)
216
+ return fig
217
+
218
+ except Exception as e:
219
+ print(f"❌ Erreur lors de la création du graphique de distribution: {e}")
220
+ return self._create_error_plot(
221
+ "❌ Erreur lors de la création du graphique",
222
+ f"Erreur technique: {str(e)[:100]}..."
223
+ )
224
+
225
+ except Exception as e:
226
+ print(f"❌ Erreur critique dans create_risk_distribution: {e}")
227
+ return self._create_error_plot(
228
+ "❌ Erreur critique",
229
+ "Impossible de créer la distribution des risques"
230
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
  def create_herbicide_timeline(self):
233
+ """Crée un graphique de l'évolution temporelle des herbicides avec gestion d'erreur"""
234
+ try:
235
+ if self.df is None or len(self.df) == 0:
236
+ return self._create_error_plot(
237
+ " Aucune donnée disponible",
238
+ "Veuillez charger les données d'abord"
239
+ )
240
+
241
+ required_cols = ['millesime', 'familleprod']
242
+ missing_cols = [col for col in required_cols if col not in self.df.columns]
243
+
244
+ if missing_cols:
245
+ return self._create_error_plot(
246
+ f" Colonnes manquantes: {missing_cols}",
247
+ "Les données temporelles ne sont pas disponibles"
248
+ )
249
+
250
+ try:
251
+ # Filtrer les herbicides et grouper par année
252
+ herbicides_df = self.df[self.df['familleprod'] == 'Herbicides']
253
+ if len(herbicides_df) == 0:
254
+ return self._create_error_plot(
255
+ "❌ Aucune donnée d'herbicide disponible",
256
+ "Aucune intervention herbicide trouvée dans les données"
257
+ )
258
+
259
+ agg_dict = {'numparcell': 'nunique'}
260
+ if 'quantitetot' in herbicides_df.columns:
261
+ agg_dict['quantitetot'] = 'sum'
262
+
263
+ yearly_herbicides = herbicides_df.groupby('millesime').agg(agg_dict).reset_index()
264
+
265
+ if len(yearly_herbicides) == 0:
266
+ return self._create_error_plot(
267
+ "❌ Aucune donnée temporelle valide",
268
+ "Impossible de grouper les données par année"
269
+ )
270
+
271
+ y_col = 'quantitetot' if 'quantitetot' in yearly_herbicides.columns else 'numparcell'
272
+ y_label = 'Quantité totale d\'herbicides' if y_col == 'quantitetot' else 'Nombre de parcelles traitées'
273
+
274
+ fig = px.line(
275
+ yearly_herbicides,
276
+ x='millesime',
277
+ y=y_col,
278
+ title="📈 Évolution de l'Usage des Herbicides par Année",
279
+ labels={
280
+ 'millesime': 'Année',
281
+ y_col: y_label
282
+ }
283
+ )
284
+
285
+ fig.update_layout(width=700, height=400)
286
+ return fig
287
+
288
+ except Exception as e:
289
+ print(f"❌ Erreur lors de la création du graphique temporel: {e}")
290
+ return self._create_error_plot(
291
+ "❌ Erreur lors de la création du graphique",
292
+ f"Erreur technique: {str(e)[:100]}..."
293
+ )
294
+
295
+ except Exception as e:
296
+ print(f"❌ Erreur critique dans create_herbicide_timeline: {e}")
297
+ return self._create_error_plot(
298
+ "❌ Erreur critique",
299
+ "Impossible de créer l'évolution temporelle"
300
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
  def create_surface_analysis(self):
303
+ """Analyse de la distribution des surfaces avec gestion d'erreur"""
304
+ try:
305
+ if self.df is None or len(self.df) == 0:
306
+ return self._create_error_plot(
307
+ "❌ Aucune donnée disponible",
308
+ "Veuillez charger les données d'abord"
309
+ )
310
+
311
+ if 'surfparc' not in self.df.columns:
312
+ return self._create_error_plot(
313
+ "❌ Colonne 'surfparc' manquante",
314
+ "Les données de surface ne sont pas disponibles"
315
+ )
316
+
317
+ try:
318
+ # Nettoyer les données de surface
319
+ surface_data = self.df['surfparc'].dropna()
320
+
321
+ if len(surface_data) == 0:
322
+ return self._create_error_plot(
323
+ "❌ Aucune donnée de surface valide",
324
+ "Toutes les valeurs de surface sont manquantes"
325
+ )
326
+
327
+ fig = px.histogram(
328
+ x=surface_data,
329
+ nbins=20,
330
+ title="📏 Distribution des Surfaces de Parcelles",
331
+ labels={
332
+ 'x': 'Surface (ha)',
333
+ 'count': 'Nombre de parcelles'
334
+ }
335
+ )
336
+
337
+ fig.update_layout(width=700, height=400)
338
+ return fig
339
+
340
+ except Exception as e:
341
+ print(f"❌ Erreur lors de la création du graphique de surface: {e}")
342
+ return self._create_error_plot(
343
+ "❌ Erreur lors de la création du graphique",
344
+ f"Erreur technique: {str(e)[:100]}..."
345
+ )
346
+
347
+ except Exception as e:
348
+ print(f"❌ Erreur critique dans create_surface_analysis: {e}")
349
+ return self._create_error_plot(
350
+ "❌ Erreur critique",
351
+ "Impossible de créer l'analyse des surfaces"
352
+ )