Tracy André commited on
Commit
150fdd6
·
1 Parent(s): f084836
Files changed (2) hide show
  1. NOUVELLE_FONCTIONNALITE_PARCELLES.md +156 -0
  2. app.py +209 -85
NOUVELLE_FONCTIONNALITE_PARCELLES.md ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🏠 Nouvelle Fonctionnalité : Filtrage par Parcelle
2
+
3
+ ## 📋 Fonctionnalité Ajoutée
4
+
5
+ Dans l'onglet **"📅 Analyse par Année"**, une nouvelle liste déroulante a été ajoutée pour permettre la sélection par parcelle spécifique, en complément du filtrage par année existant.
6
+
7
+ ## 🎯 Objectif
8
+
9
+ Permettre une analyse granulaire des données agricoles en combinant :
10
+ - **Filtrage temporel** : Par année
11
+ - **Filtrage spatial** : Par parcelle spécifique
12
+
13
+ ## 🔧 Fonctionnement
14
+
15
+ ### Interface Utilisateur
16
+
17
+ L'interface comprend maintenant **deux contrôles de filtrage** côte à côte :
18
+
19
+ 1. **🗓️ Sélectionner une année**
20
+ - Liste déroulante des années disponibles
21
+ - Année la plus récente sélectionnée par défaut
22
+
23
+ 2. **🏠 Sélectionner une parcelle** *(NOUVEAU)*
24
+ - Liste déroulante de toutes les parcelles disponibles
25
+ - Format d'affichage : `Numéro - Nom de la parcelle`
26
+ - Option "Toutes les parcelles" par défaut
27
+ - 84 parcelles disponibles dans les données actuelles
28
+
29
+ ### Synchronisation des Contrôles
30
+
31
+ - **Mise à jour automatique** : Changer l'année OU la parcelle rafraîchit tous les graphiques
32
+ - **Réactivité** : Les modifications sont appliquées immédiatement
33
+ - **Cohérence** : Tous les graphiques et statistiques reflètent la sélection combinée
34
+
35
+ ## 📊 Impacts sur les Visualisations
36
+
37
+ ### 1. Statistiques Résumé
38
+ - **Titre dynamique** : Indique l'année et la parcelle sélectionnées
39
+ - **Métriques adaptées** :
40
+ - Nombre de parcelles (1 si parcelle spécifique, N si toutes)
41
+ - Surface totale/moyenne de la sélection
42
+ - Interventions herbicides pour la sélection
43
+
44
+ **Exemple :**
45
+ ```
46
+ ## 📊 Statistiques pour l'année 2014 - Parcelle 2 (Kersuzan Bas)
47
+ - Nombre de parcelles: 1
48
+ - Nombre d'interventions: 27
49
+ - Surface totale: 49.68 hectares
50
+ - Interventions herbicides: 6 (22.2%)
51
+ ```
52
+
53
+ ### 2. Graphique des Cultures
54
+ - **Scope adapté** : Répartition des cultures pour la parcelle sélectionnée
55
+ - **Titre dynamique** : Inclut l'année et la parcelle
56
+ - **Données filtrées** : Seules les cultures de la parcelle sélectionnée
57
+
58
+ ### 3. Timeline des Interventions
59
+ - **Activité focalisée** : Répartition mensuelle pour la parcelle spécifique
60
+ - **Pattern individuel** : Comprendre les habitudes d'une parcelle particulière
61
+ - **Comparaison temporelle** : Évolution des pratiques sur une parcelle
62
+
63
+ ### 4. Analyse des Herbicides
64
+ - **Usage spécifique** : Top des herbicides utilisés sur la parcelle
65
+ - **Pratiques individuelles** : Stratégies herbicides parcelle par parcelle
66
+ - **Traçabilité** : Historique précis des traitements
67
+
68
+ ## 💡 Cas d'Usage
69
+
70
+ ### 🔍 Analyse Parcellaire Détaillée
71
+ ```
72
+ Sélection : 2014 + Parcelle 2 (Kersuzan Bas)
73
+ Résultat : Vue complète des pratiques sur cette parcelle en 2014
74
+ Usage : Audit détaillé, préparation de rotation, analyse de conformité
75
+ ```
76
+
77
+ ### 📈 Comparaison Entre Parcelles
78
+ ```
79
+ Étape 1 : Analyser Parcelle A en 2014
80
+ Étape 2 : Analyser Parcelle B en 2014
81
+ Résultat : Comparaison des pratiques entre parcelles similaires
82
+ ```
83
+
84
+ ### 🎯 Suivi Longitudinal
85
+ ```
86
+ Sélection : Parcelle 2 en 2014, puis 2015, puis 2016...
87
+ Résultat : Évolution des pratiques sur une parcelle spécifique
88
+ Usage : Analyse de l'impact des changements de pratiques
89
+ ```
90
+
91
+ ### 🌾 Préparation de Culture
92
+ ```
93
+ Contexte : Vouloir planter du pois sur Parcelle 2
94
+ Action : Analyser l'historique herbicide de cette parcelle
95
+ Décision : Évaluer la compatibilité avec la culture sensible
96
+ ```
97
+
98
+ ## ⚙️ Aspects Techniques
99
+
100
+ ### Gestion des Types de Données
101
+ - **Conversion automatique** : String ↔ Integer pour les numéros de parcelles
102
+ - **Robustesse** : Gestion des erreurs de conversion
103
+ - **Compatibilité** : Fonctionne avec différents formats de données
104
+
105
+ ### Performance
106
+ - **Filtrage optimisé** : Pandas operations pour des performances élevées
107
+ - **Mémoire efficace** : Pas de duplication inutile des données
108
+ - **Réactivité** : Mise à jour instantanée des visualisations
109
+
110
+ ### Architecture
111
+ ```python
112
+ # Nouvelle méthode de filtrage combiné
113
+ def filter_data_by_year_and_parcel(self, year, parcel_id):
114
+ # Filtrage temporel ET spatial
115
+ # Gestion des types de données
116
+ # Validation des entrées
117
+
118
+ # Méthodes de visualisation mises à jour
119
+ def create_year_culture_analysis(self, year, parcel_id=None):
120
+ def create_year_interventions_timeline(self, year, parcel_id=None):
121
+ def create_year_herbicide_analysis(self, year, parcel_id=None):
122
+ def get_year_summary_stats(self, year, parcel_id=None):
123
+ ```
124
+
125
+ ## 🎉 Avantages
126
+
127
+ ### Pour l'Utilisateur
128
+ - **Granularité** : Analyse au niveau parcelle le plus fin
129
+ - **Flexibilité** : Vue globale OU spécifique selon les besoins
130
+ - **Intuitivité** : Interface simple avec deux listes déroulantes
131
+ - **Rapidité** : Exploration interactive des données
132
+
133
+ ### Pour l'Analyse
134
+ - **Précision** : Données exactes pour une parcelle donnée
135
+ - **Comparabilité** : Benchmarking facile entre parcelles
136
+ - **Traçabilité** : Suivi détaillé des pratiques
137
+ - **Conformité** : Audit parcelle par parcelle
138
+
139
+ ### Pour la Décision
140
+ - **Ciblage** : Recommandations spécifiques à une parcelle
141
+ - **Planning** : Préparation des rotations avec historique précis
142
+ - **Optimisation** : Ajustement des pratiques parcelle par parcelle
143
+ - **Réglementation** : Respect des contraintes par parcelle
144
+
145
+ ## 🔮 Évolutions Futures Possibles
146
+
147
+ 1. **Filtrage multiple** : Sélection de plusieurs parcelles simultanément
148
+ 2. **Comparaison visuelle** : Graphiques côte-à-côte pour plusieurs parcelles
149
+ 3. **Alertes personnalisées** : Notifications pour parcelles à surveiller
150
+ 4. **Export parcellaire** : Rapports PDF par parcelle
151
+ 5. **Cartographie** : Visualisation géospatiale des parcelles
152
+
153
+ ---
154
+
155
+ *Développé pour le Hackathon CRA Bretagne 🏆*
156
+ *Amélioration continue de l'aide à la décision agricole*
app.py CHANGED
@@ -470,6 +470,35 @@ class AgricultureAnalyzer:
470
  years = sorted(self.df['millesime'].dropna().unique())
471
  return [int(year) for year in years if pd.notna(year)]
472
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  def filter_data_by_year(self, year):
474
  """Filtre les données par année"""
475
  if self.df is None or year is None:
@@ -478,95 +507,157 @@ class AgricultureAnalyzer:
478
  year_data = self.df[self.df['millesime'] == year].copy()
479
  return year_data
480
 
481
- def get_year_summary_stats(self, year):
482
- """Retourne les statistiques de résumé pour une année spécifique"""
483
- year_data = self.filter_data_by_year(year)
 
484
 
485
- if year_data is None or len(year_data) == 0:
486
- return f"❌ Aucune donnée disponible pour l'année {year}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
 
488
  stats_text = f"""
489
- ## 📊 Statistiques pour l'année {year}
490
- - **Nombre de parcelles**: {year_data['numparcell'].nunique()}
491
- - **Nombre d'interventions**: {len(year_data):,}
492
- - **Surface totale**: {year_data['surfparc'].sum():.2f} hectares
493
- - **Surface moyenne par parcelle**: {year_data['surfparc'].mean():.2f} hectares
494
 
495
  ## 🧪 Analyse Herbicides
496
  """
497
 
498
- if 'familleprod' in year_data.columns:
499
- herbicides_df = year_data[year_data['familleprod'] == 'Herbicides']
500
  if len(herbicides_df) > 0:
501
  stats_text += f"""
502
- - **Interventions herbicides**: {len(herbicides_df)} ({(len(herbicides_df)/len(year_data)*100):.1f}%)
503
  - **Parcelles traitées**: {herbicides_df['numparcell'].nunique()}
504
  - **Produits herbicides différents**: {herbicides_df['produit'].nunique()}
505
  """
506
  else:
507
- stats_text += "\n- **Aucune intervention herbicide cette année**"
508
 
509
  return stats_text
510
 
511
- def create_year_culture_analysis(self, year):
512
- """Analyse par type de culture pour une année spécifique"""
513
- year_data = self.filter_data_by_year(year)
 
 
 
 
 
 
514
 
515
- if year_data is None or len(year_data) == 0:
516
- fig = px.pie(title=f"❌ Aucune donnée disponible pour {year}")
517
- fig.add_annotation(text=f"Aucune donnée pour l'année {year}",
518
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
519
  return fig
520
 
521
- if 'libelleusag' not in year_data.columns:
522
- fig = px.pie(title=f"❌ Données de culture manquantes pour {year}")
523
  fig.add_annotation(text="Colonne 'libelleusag' manquante",
524
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
525
  return fig
526
 
527
- culture_counts = year_data['libelleusag'].value_counts()
528
 
529
  fig = px.pie(values=culture_counts.values,
530
  names=culture_counts.index,
531
- title=f"🌱 Répartition des Cultures - {year}")
532
 
533
  fig.update_layout(width=700, height=500)
534
  return fig
535
 
536
- def create_year_interventions_timeline(self, year):
537
- """Crée un graphique temporel des interventions pour une année"""
538
- year_data = self.filter_data_by_year(year)
539
 
540
- if year_data is None or len(year_data) == 0:
541
- fig = px.bar(title=f"❌ Aucune donnée disponible pour {year}")
542
- fig.add_annotation(text=f"Aucune donnée pour l'année {year}",
 
 
 
 
 
 
543
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
544
  return fig
545
 
546
- if 'datedebut' not in year_data.columns:
547
  # Fallback: graphique des types d'événements
548
- if 'libevenem' in year_data.columns:
549
- event_counts = year_data['libevenem'].value_counts()
550
  fig = px.bar(x=event_counts.index,
551
  y=event_counts.values,
552
- title=f"📊 Types d'Interventions - {year}",
553
  labels={'x': 'Type d\'intervention', 'y': 'Nombre'})
554
  fig.update_layout(width=800, height=500)
555
  fig.update_xaxis(tickangle=45)
556
  return fig
557
  else:
558
- fig = px.bar(title=f"❌ Données d'intervention manquantes pour {year}")
559
  fig.add_annotation(text="Colonnes de dates manquantes",
560
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
561
  return fig
562
 
563
  # Convertir les dates et créer le graphique temporel
564
  try:
565
- year_data['datedebut_parsed'] = pd.to_datetime(year_data['datedebut'],
566
  format='%d/%m/%y', errors='coerce')
567
- year_data['mois'] = year_data['datedebut_parsed'].dt.month
568
 
569
- monthly_counts = year_data.groupby('mois').size().reset_index()
570
  monthly_counts.columns = ['mois', 'nb_interventions']
571
 
572
  # Ajouter les noms des mois
@@ -577,7 +668,7 @@ class AgricultureAnalyzer:
577
  fig = px.bar(monthly_counts,
578
  x='mois_nom',
579
  y='nb_interventions',
580
- title=f"📅 Répartition Mensuelle des Interventions - {year}",
581
  labels={'mois_nom': 'Mois', 'nb_interventions': 'Nombre d\'interventions'})
582
 
583
  fig.update_layout(width=800, height=500)
@@ -586,40 +677,46 @@ class AgricultureAnalyzer:
586
  except Exception as e:
587
  print(f"❌ Erreur lors de la création du graphique temporel: {e}")
588
  # Fallback vers le graphique des événements
589
- if 'libevenem' in year_data.columns:
590
- event_counts = year_data['libevenem'].value_counts()
591
  fig = px.bar(x=event_counts.index,
592
  y=event_counts.values,
593
- title=f"📊 Types d'Interventions - {year}",
594
  labels={'x': 'Type d\'intervention', 'y': 'Nombre'})
595
  fig.update_layout(width=800, height=500)
596
  fig.update_xaxis(tickangle=45)
597
  return fig
598
  else:
599
- fig = px.bar(title=f"❌ Erreur lors du traitement des données {year}")
600
  return fig
601
 
602
- def create_year_herbicide_analysis(self, year):
603
- """Analyse des herbicides pour une année spécifique"""
604
- year_data = self.filter_data_by_year(year)
605
 
606
- if year_data is None or len(year_data) == 0:
607
- fig = px.bar(title=f"❌ Aucune donnée disponible pour {year}")
608
- fig.add_annotation(text=f"Aucune donnée pour l'année {year}",
 
 
 
 
 
 
609
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
610
  return fig
611
 
612
- if 'familleprod' not in year_data.columns:
613
- fig = px.bar(title=f"❌ Données de produits manquantes pour {year}")
614
  fig.add_annotation(text="Colonne 'familleprod' manquante",
615
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
616
  return fig
617
 
618
- herbicides_df = year_data[year_data['familleprod'] == 'Herbicides']
619
 
620
  if len(herbicides_df) == 0:
621
- fig = px.bar(title=f"✅ Aucun herbicide utilisé en {year}")
622
- fig.add_annotation(text=f"Aucune intervention herbicide en {year}",
623
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
624
  return fig
625
 
@@ -629,13 +726,13 @@ class AgricultureAnalyzer:
629
  fig = px.bar(x=herbicide_counts.values,
630
  y=herbicide_counts.index,
631
  orientation='h',
632
- title=f"🧪 Top 10 Herbicides Utilisés - {year}",
633
  labels={'x': 'Nombre d\'utilisations', 'y': 'Produit'})
634
 
635
  fig.update_layout(width=800, height=500)
636
  return fig
637
  else:
638
- fig = px.bar(title=f"❌ Détails des produits manquants pour {year}")
639
  fig.add_annotation(text="Colonne 'produit' manquante",
640
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
641
  return fig
@@ -713,25 +810,41 @@ def create_interface():
713
  """)
714
 
715
  with gr.TabItem("📅 Analyse par Année"):
716
- gr.Markdown("## Visualisation des données par année")
717
 
718
- # Liste déroulante pour sélectionner l'année
719
- available_years = analyzer.get_available_years()
720
- if available_years:
721
- default_year = available_years[-1] # Dernière année par défaut
722
- year_dropdown = gr.Dropdown(
723
- choices=available_years,
724
- value=default_year,
725
- label="🗓️ Sélectionner une année",
726
- info="Choisissez l'année à analyser dans la liste déroulante"
727
- )
728
- else:
729
- year_dropdown = gr.Dropdown(
730
- choices=[],
731
- value=None,
732
- label="🗓️ Sélectionner une année",
733
- info="Aucune année disponible"
734
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735
 
736
  # Statistiques pour l'année sélectionnée
737
  year_stats = gr.Markdown("")
@@ -744,8 +857,8 @@ def create_interface():
744
  with gr.Row():
745
  year_herbicide_plot = gr.Plot()
746
 
747
- # Fonction de mise à jour quand l'année change
748
- def update_year_analysis(selected_year):
749
  if selected_year is None:
750
  empty_fig = px.bar(title="❌ Aucune année sélectionnée")
751
  return (
@@ -753,23 +866,30 @@ def create_interface():
753
  empty_fig, empty_fig, empty_fig
754
  )
755
 
756
- stats = analyzer.get_year_summary_stats(selected_year)
757
- culture_fig = analyzer.create_year_culture_analysis(selected_year)
758
- timeline_fig = analyzer.create_year_interventions_timeline(selected_year)
759
- herbicide_fig = analyzer.create_year_herbicide_analysis(selected_year)
760
 
761
  return stats, culture_fig, timeline_fig, herbicide_fig
762
 
763
- # Connecter la liste déroulante aux mises à jour
764
  year_dropdown.change(
765
  update_year_analysis,
766
- inputs=[year_dropdown],
767
  outputs=[year_stats, year_culture_plot, year_timeline_plot, year_herbicide_plot]
768
  )
769
 
770
- # Initialiser avec l'année par défaut
 
 
 
 
 
 
771
  if available_years:
772
- initial_stats, initial_culture, initial_timeline, initial_herbicide = update_year_analysis(default_year)
 
773
  year_stats.value = initial_stats
774
  year_culture_plot.value = initial_culture
775
  year_timeline_plot.value = initial_timeline
@@ -777,10 +897,14 @@ def create_interface():
777
 
778
  gr.Markdown("""
779
  **Informations sur cet onglet:**
780
- - 📊 **Statistiques**: Résumé pour l'année sélectionnée
 
 
781
  - 🌱 **Cultures**: Répartition des types de cultures
782
  - 📅 **Timeline**: Répartition temporelle des interventions
783
  - 🧪 **Herbicides**: Top 10 des herbicides utilisés
 
 
784
  """)
785
 
786
  with gr.TabItem("🔍 Requêtes Herbicides"):
 
470
  years = sorted(self.df['millesime'].dropna().unique())
471
  return [int(year) for year in years if pd.notna(year)]
472
 
473
+ def get_available_parcels(self):
474
+ """Retourne la liste des parcelles disponibles dans les données"""
475
+ if self.df is None or len(self.df) == 0:
476
+ return []
477
+
478
+ # Créer une liste avec numéro et nom de parcelle si disponible
479
+ parcels_info = []
480
+
481
+ if 'nomparc' in self.df.columns:
482
+ # Grouper par parcelle et prendre le premier nom (en cas de doublons)
483
+ parcels_data = self.df.groupby('numparcell')['nomparc'].first().reset_index()
484
+ for _, row in parcels_data.iterrows():
485
+ parcel_id = str(row['numparcell'])
486
+ parcel_name = str(row['nomparc']) if pd.notna(row['nomparc']) else ""
487
+ if parcel_name and parcel_name != "nan":
488
+ display_name = f"{parcel_id} - {parcel_name}"
489
+ else:
490
+ display_name = parcel_id
491
+ parcels_info.append((display_name, parcel_id))
492
+ else:
493
+ # Seulement les numéros de parcelles
494
+ unique_parcels = sorted(self.df['numparcell'].dropna().unique())
495
+ parcels_info = [(str(p), str(p)) for p in unique_parcels]
496
+
497
+ # Ajouter l'option "Toutes les parcelles" en premier
498
+ parcels_info.insert(0, ("Toutes les parcelles", "ALL"))
499
+
500
+ return parcels_info
501
+
502
  def filter_data_by_year(self, year):
503
  """Filtre les données par année"""
504
  if self.df is None or year is None:
 
507
  year_data = self.df[self.df['millesime'] == year].copy()
508
  return year_data
509
 
510
+ def filter_data_by_parcel(self, parcel_id):
511
+ """Filtre les données par parcelle"""
512
+ if self.df is None or parcel_id is None or parcel_id == "ALL":
513
+ return self.df
514
 
515
+ parcel_data = self.df[self.df['numparcell'] == parcel_id].copy()
516
+ return parcel_data
517
+
518
+ def filter_data_by_year_and_parcel(self, year, parcel_id):
519
+ """Filtre les données par année et parcelle"""
520
+ if self.df is None:
521
+ return None
522
+
523
+ filtered_data = self.df.copy()
524
+
525
+ # Filtrer par année si spécifiée
526
+ if year is not None:
527
+ filtered_data = filtered_data[filtered_data['millesime'] == year]
528
+
529
+ # Filtrer par parcelle si spécifiée (et différente de "ALL")
530
+ if parcel_id is not None and parcel_id != "ALL":
531
+ # Convertir parcel_id en type approprié (gérer string/int)
532
+ try:
533
+ # Essayer de convertir en entier si c'est une chaîne
534
+ if isinstance(parcel_id, str) and parcel_id.isdigit():
535
+ parcel_id_converted = int(parcel_id)
536
+ else:
537
+ parcel_id_converted = parcel_id
538
+
539
+ filtered_data = filtered_data[filtered_data['numparcell'] == parcel_id_converted]
540
+ except (ValueError, TypeError):
541
+ # En cas d'erreur de conversion, essayer tel quel
542
+ filtered_data = filtered_data[filtered_data['numparcell'] == parcel_id]
543
+
544
+ return filtered_data
545
+
546
+ def get_year_summary_stats(self, year, parcel_id=None):
547
+ """Retourne les statistiques de résumé pour une année spécifique et optionnellement une parcelle"""
548
+ filtered_data = self.filter_data_by_year_and_parcel(year, parcel_id)
549
+
550
+ if filtered_data is None or len(filtered_data) == 0:
551
+ if parcel_id and parcel_id != "ALL":
552
+ return f"❌ Aucune donnée disponible pour l'année {year} et la parcelle {parcel_id}"
553
+ else:
554
+ return f"❌ Aucune donnée disponible pour l'année {year}"
555
+
556
+ # Titre dynamique selon le filtrage
557
+ if parcel_id and parcel_id != "ALL":
558
+ title = f"## 📊 Statistiques pour l'année {year} - Parcelle {parcel_id}"
559
+ # Obtenir le nom de la parcelle si disponible
560
+ if 'nomparc' in filtered_data.columns:
561
+ parcel_name = filtered_data['nomparc'].iloc[0] if len(filtered_data) > 0 else ""
562
+ if parcel_name and str(parcel_name) != "nan":
563
+ title = f"## 📊 Statistiques pour l'année {year} - Parcelle {parcel_id} ({parcel_name})"
564
+ else:
565
+ title = f"## 📊 Statistiques pour l'année {year}"
566
 
567
  stats_text = f"""
568
+ {title}
569
+ - **Nombre de parcelles**: {filtered_data['numparcell'].nunique()}
570
+ - **Nombre d'interventions**: {len(filtered_data):,}
571
+ - **Surface totale**: {filtered_data['surfparc'].sum():.2f} hectares
572
+ - **Surface moyenne par parcelle**: {filtered_data['surfparc'].mean():.2f} hectares
573
 
574
  ## 🧪 Analyse Herbicides
575
  """
576
 
577
+ if 'familleprod' in filtered_data.columns:
578
+ herbicides_df = filtered_data[filtered_data['familleprod'] == 'Herbicides']
579
  if len(herbicides_df) > 0:
580
  stats_text += f"""
581
+ - **Interventions herbicides**: {len(herbicides_df)} ({(len(herbicides_df)/len(filtered_data)*100):.1f}%)
582
  - **Parcelles traitées**: {herbicides_df['numparcell'].nunique()}
583
  - **Produits herbicides différents**: {herbicides_df['produit'].nunique()}
584
  """
585
  else:
586
+ stats_text += "\n- **Aucune intervention herbicide cette période**"
587
 
588
  return stats_text
589
 
590
+ def create_year_culture_analysis(self, year, parcel_id=None):
591
+ """Analyse par type de culture pour une année spécifique et optionnellement une parcelle"""
592
+ filtered_data = self.filter_data_by_year_and_parcel(year, parcel_id)
593
+
594
+ # Titre dynamique
595
+ if parcel_id and parcel_id != "ALL":
596
+ title_suffix = f" - {year} (Parcelle {parcel_id})"
597
+ else:
598
+ title_suffix = f" - {year}"
599
 
600
+ if filtered_data is None or len(filtered_data) == 0:
601
+ fig = px.pie(title=f"❌ Aucune donnée disponible{title_suffix}")
602
+ fig.add_annotation(text=f"Aucune donnée pour cette sélection",
603
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
604
  return fig
605
 
606
+ if 'libelleusag' not in filtered_data.columns:
607
+ fig = px.pie(title=f"❌ Données de culture manquantes{title_suffix}")
608
  fig.add_annotation(text="Colonne 'libelleusag' manquante",
609
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
610
  return fig
611
 
612
+ culture_counts = filtered_data['libelleusag'].value_counts()
613
 
614
  fig = px.pie(values=culture_counts.values,
615
  names=culture_counts.index,
616
+ title=f"🌱 Répartition des Cultures{title_suffix}")
617
 
618
  fig.update_layout(width=700, height=500)
619
  return fig
620
 
621
+ def create_year_interventions_timeline(self, year, parcel_id=None):
622
+ """Crée un graphique temporel des interventions pour une année et optionnellement une parcelle"""
623
+ filtered_data = self.filter_data_by_year_and_parcel(year, parcel_id)
624
 
625
+ # Titre dynamique
626
+ if parcel_id and parcel_id != "ALL":
627
+ title_suffix = f" - {year} (Parcelle {parcel_id})"
628
+ else:
629
+ title_suffix = f" - {year}"
630
+
631
+ if filtered_data is None or len(filtered_data) == 0:
632
+ fig = px.bar(title=f"❌ Aucune donnée disponible{title_suffix}")
633
+ fig.add_annotation(text=f"Aucune donnée pour cette sélection",
634
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
635
  return fig
636
 
637
+ if 'datedebut' not in filtered_data.columns:
638
  # Fallback: graphique des types d'événements
639
+ if 'libevenem' in filtered_data.columns:
640
+ event_counts = filtered_data['libevenem'].value_counts()
641
  fig = px.bar(x=event_counts.index,
642
  y=event_counts.values,
643
+ title=f"📊 Types d'Interventions{title_suffix}",
644
  labels={'x': 'Type d\'intervention', 'y': 'Nombre'})
645
  fig.update_layout(width=800, height=500)
646
  fig.update_xaxis(tickangle=45)
647
  return fig
648
  else:
649
+ fig = px.bar(title=f"❌ Données d'intervention manquantes{title_suffix}")
650
  fig.add_annotation(text="Colonnes de dates manquantes",
651
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
652
  return fig
653
 
654
  # Convertir les dates et créer le graphique temporel
655
  try:
656
+ filtered_data['datedebut_parsed'] = pd.to_datetime(filtered_data['datedebut'],
657
  format='%d/%m/%y', errors='coerce')
658
+ filtered_data['mois'] = filtered_data['datedebut_parsed'].dt.month
659
 
660
+ monthly_counts = filtered_data.groupby('mois').size().reset_index()
661
  monthly_counts.columns = ['mois', 'nb_interventions']
662
 
663
  # Ajouter les noms des mois
 
668
  fig = px.bar(monthly_counts,
669
  x='mois_nom',
670
  y='nb_interventions',
671
+ title=f"📅 Répartition Mensuelle des Interventions{title_suffix}",
672
  labels={'mois_nom': 'Mois', 'nb_interventions': 'Nombre d\'interventions'})
673
 
674
  fig.update_layout(width=800, height=500)
 
677
  except Exception as e:
678
  print(f"❌ Erreur lors de la création du graphique temporel: {e}")
679
  # Fallback vers le graphique des événements
680
+ if 'libevenem' in filtered_data.columns:
681
+ event_counts = filtered_data['libevenem'].value_counts()
682
  fig = px.bar(x=event_counts.index,
683
  y=event_counts.values,
684
+ title=f"📊 Types d'Interventions{title_suffix}",
685
  labels={'x': 'Type d\'intervention', 'y': 'Nombre'})
686
  fig.update_layout(width=800, height=500)
687
  fig.update_xaxis(tickangle=45)
688
  return fig
689
  else:
690
+ fig = px.bar(title=f"❌ Erreur lors du traitement des données{title_suffix}")
691
  return fig
692
 
693
+ def create_year_herbicide_analysis(self, year, parcel_id=None):
694
+ """Analyse des herbicides pour une année spécifique et optionnellement une parcelle"""
695
+ filtered_data = self.filter_data_by_year_and_parcel(year, parcel_id)
696
 
697
+ # Titre dynamique
698
+ if parcel_id and parcel_id != "ALL":
699
+ title_suffix = f" - {year} (Parcelle {parcel_id})"
700
+ else:
701
+ title_suffix = f" - {year}"
702
+
703
+ if filtered_data is None or len(filtered_data) == 0:
704
+ fig = px.bar(title=f"❌ Aucune donnée disponible{title_suffix}")
705
+ fig.add_annotation(text=f"Aucune donnée pour cette sélection",
706
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
707
  return fig
708
 
709
+ if 'familleprod' not in filtered_data.columns:
710
+ fig = px.bar(title=f"❌ Données de produits manquantes{title_suffix}")
711
  fig.add_annotation(text="Colonne 'familleprod' manquante",
712
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
713
  return fig
714
 
715
+ herbicides_df = filtered_data[filtered_data['familleprod'] == 'Herbicides']
716
 
717
  if len(herbicides_df) == 0:
718
+ fig = px.bar(title=f"✅ Aucun herbicide utilisé{title_suffix}")
719
+ fig.add_annotation(text=f"Aucune intervention herbicide pour cette sélection",
720
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
721
  return fig
722
 
 
726
  fig = px.bar(x=herbicide_counts.values,
727
  y=herbicide_counts.index,
728
  orientation='h',
729
+ title=f"🧪 Top 10 Herbicides Utilisés{title_suffix}",
730
  labels={'x': 'Nombre d\'utilisations', 'y': 'Produit'})
731
 
732
  fig.update_layout(width=800, height=500)
733
  return fig
734
  else:
735
+ fig = px.bar(title=f"❌ Détails des produits manquants{title_suffix}")
736
  fig.add_annotation(text="Colonne 'produit' manquante",
737
  xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
738
  return fig
 
810
  """)
811
 
812
  with gr.TabItem("📅 Analyse par Année"):
813
+ gr.Markdown("## Visualisation des données par année et par parcelle")
814
 
815
+ # Contrôles de filtrage
816
+ with gr.Row():
817
+ with gr.Column():
818
+ # Liste déroulante pour sélectionner l'année
819
+ available_years = analyzer.get_available_years()
820
+ if available_years:
821
+ default_year = available_years[-1] # Dernière année par défaut
822
+ year_dropdown = gr.Dropdown(
823
+ choices=available_years,
824
+ value=default_year,
825
+ label="🗓️ Sélectionner une année",
826
+ info="Choisissez l'année à analyser dans la liste déroulante"
827
+ )
828
+ else:
829
+ year_dropdown = gr.Dropdown(
830
+ choices=[],
831
+ value=None,
832
+ label="🗓️ Sélectionner une année",
833
+ info="Aucune année disponible"
834
+ )
835
+
836
+ with gr.Column():
837
+ # Liste déroulante pour sélectionner la parcelle
838
+ available_parcels = analyzer.get_available_parcels()
839
+ parcel_choices = [item[0] for item in available_parcels] # Noms d'affichage
840
+ parcel_values = [item[1] for item in available_parcels] # Valeurs réelles
841
+
842
+ parcel_dropdown = gr.Dropdown(
843
+ choices=list(zip(parcel_choices, parcel_values)),
844
+ value="ALL" if available_parcels else None,
845
+ label="🏠 Sélectionner une parcelle",
846
+ info="Choisissez une parcelle spécifique ou 'Toutes les parcelles'"
847
+ )
848
 
849
  # Statistiques pour l'année sélectionnée
850
  year_stats = gr.Markdown("")
 
857
  with gr.Row():
858
  year_herbicide_plot = gr.Plot()
859
 
860
+ # Fonction de mise à jour quand l'année ou la parcelle change
861
+ def update_year_analysis(selected_year, selected_parcel):
862
  if selected_year is None:
863
  empty_fig = px.bar(title="❌ Aucune année sélectionnée")
864
  return (
 
866
  empty_fig, empty_fig, empty_fig
867
  )
868
 
869
+ stats = analyzer.get_year_summary_stats(selected_year, selected_parcel)
870
+ culture_fig = analyzer.create_year_culture_analysis(selected_year, selected_parcel)
871
+ timeline_fig = analyzer.create_year_interventions_timeline(selected_year, selected_parcel)
872
+ herbicide_fig = analyzer.create_year_herbicide_analysis(selected_year, selected_parcel)
873
 
874
  return stats, culture_fig, timeline_fig, herbicide_fig
875
 
876
+ # Connecter les listes déroulantes aux mises à jour
877
  year_dropdown.change(
878
  update_year_analysis,
879
+ inputs=[year_dropdown, parcel_dropdown],
880
  outputs=[year_stats, year_culture_plot, year_timeline_plot, year_herbicide_plot]
881
  )
882
 
883
+ parcel_dropdown.change(
884
+ update_year_analysis,
885
+ inputs=[year_dropdown, parcel_dropdown],
886
+ outputs=[year_stats, year_culture_plot, year_timeline_plot, year_herbicide_plot]
887
+ )
888
+
889
+ # Initialiser avec l'année et la parcelle par défaut
890
  if available_years:
891
+ default_parcel = "ALL"
892
+ initial_stats, initial_culture, initial_timeline, initial_herbicide = update_year_analysis(default_year, default_parcel)
893
  year_stats.value = initial_stats
894
  year_culture_plot.value = initial_culture
895
  year_timeline_plot.value = initial_timeline
 
897
 
898
  gr.Markdown("""
899
  **Informations sur cet onglet:**
900
+ - 🗓️ **Filtre Année**: Sélectionnez l'année à analyser
901
+ - 🏠 **Filtre Parcelle**: Choisissez une parcelle spécifique ou toutes les parcelles
902
+ - 📊 **Statistiques**: Résumé pour la sélection (année + parcelle)
903
  - 🌱 **Cultures**: Répartition des types de cultures
904
  - 📅 **Timeline**: Répartition temporelle des interventions
905
  - 🧪 **Herbicides**: Top 10 des herbicides utilisés
906
+
907
+ 💡 **Utilisation**: Combinez les filtres pour explorer les données en détail
908
  """)
909
 
910
  with gr.TabItem("🔍 Requêtes Herbicides"):