""" Module d'interface utilisateur avec Gradio """ import os # Désactiver les analytics Gradio dès le début os.environ["GRADIO_ANALYTICS_ENABLED"] = "False" import gradio as gr from data_loader import DataLoader from analyzer import AgricultureAnalyzer from visualizations import AgricultureVisualizer from config import GRADIO_CONFIG class AgricultureInterface: """Classe responsable de l'interface utilisateur Gradio""" def __init__(self): self.data_loader = DataLoader() self.analyzer = AgricultureAnalyzer() self.visualizer = AgricultureVisualizer() self._initialize_data() def _initialize_data(self): """Initialise les données au démarrage""" print("🔄 Initialisation des données...") self.data_loader.load_data() if self.data_loader.has_data(): self.analyzer.set_data(self.data_loader.get_data()) self.analyzer.analyze_data() self.visualizer.set_data( self.data_loader.get_data(), self.analyzer.get_risk_analysis() ) print("✅ Initialisation réussie") else: print("⚠️ Aucune donnée disponible au démarrage") def refresh_data(self): """Rafraîchit toutes les données""" print("🔄 Rafraîchissement des données...") self.data_loader.load_data() if self.data_loader.has_data(): self.analyzer.set_data(self.data_loader.get_data()) self.analyzer.analyze_data() self.visualizer.set_data( self.data_loader.get_data(), self.analyzer.get_risk_analysis() ) return ( self._safe_get_summary_stats(), self._safe_create_culture_analysis(), self._safe_create_risk_distribution(), self._safe_create_risk_visualization(), self._safe_get_recommendations() ) else: return self._get_error_outputs("Aucune donnée disponible") def _get_error_outputs(self, error_message): """Retourne des outputs d'erreur standardisés""" empty_fig = self.visualizer._create_error_plot("❌ Erreur", error_message) return ( f"❌ {error_message}", empty_fig, empty_fig, empty_fig, f"❌ {error_message}" ) def _safe_get_summary_stats(self): """Récupère les statistiques avec gestion d'erreur""" return self.analyzer.get_summary_stats() def _safe_create_culture_analysis(self): """Crée l'analyse des cultures avec gestion d'erreur""" return self.visualizer.create_culture_analysis() def _safe_create_risk_distribution(self): """Crée la distribution des risques avec gestion d'erreur""" return self.visualizer.create_risk_distribution() def _safe_create_risk_visualization(self): """Crée la visualisation des risques avec gestion d'erreur""" return self.visualizer.create_risk_visualization() def _safe_get_recommendations(self): """Récupère les recommandations avec gestion d'erreur""" return self.analyzer.get_low_risk_recommendations() def create_interface(self): """Crée l'interface Gradio""" with gr.Blocks(title="🌾 Analyse Adventices Agricoles CRA", theme=gr.themes.Soft()) as demo: gr.Markdown(""" # 🌾 Analyse des Adventices Agricoles - CRA Bretagne **Objectif**: Anticiper et réduire la pression des adventices dans les parcelles agricoles bretonnes Cette application analyse les données historiques pour identifier les parcelles les plus adaptées à la culture de plantes sensibles comme le pois ou le haricot. """) with gr.Tabs(): with gr.TabItem("📊 Vue d'ensemble"): self._create_overview_tab() with gr.TabItem("🎯 Analyse des Risques"): self._create_risk_analysis_tab() with gr.TabItem("🌾 Recommandations"): self._create_recommendations_tab() with gr.TabItem("📋 Données par Année"): self._create_data_viewer_tab() with gr.TabItem("ℹ️ À propos"): self._create_about_tab() # Bouton de rafraîchissement refresh_btn = gr.Button("🔄 Actualiser les données", variant="secondary") # Connecter le bouton de rafraîchissement refresh_btn.click( self.refresh_data, outputs=[ self.stats_output, self.culture_plot, self.risk_dist_plot, self.risk_plot, self.reco_output ] ) return demo def _create_overview_tab(self): """Crée l'onglet de vue d'ensemble""" gr.Markdown("## Statistiques générales des données agricoles") self.stats_output = gr.Markdown(self._safe_get_summary_stats()) with gr.Row(): self.culture_plot = gr.Plot(self._safe_create_culture_analysis()) self.risk_dist_plot = gr.Plot(self._safe_create_risk_distribution()) def _create_risk_analysis_tab(self): """Crée l'onglet d'analyse des risques""" gr.Markdown("## Cartographie des risques adventices par parcelle") self.risk_plot = gr.Plot(self._safe_create_risk_visualization()) gr.Markdown(""" **Interprétation du graphique**: - **Axe X**: Surface de la parcelle (hectares) - **Axe Y**: IFT Herbicide approximatif - **Couleur**: Niveau de risque adventice - **Taille**: Nombre d'herbicides utilisés Les parcelles vertes (risque faible) sont idéales pour les cultures sensibles. """) def _create_recommendations_tab(self): """Crée l'onglet des recommandations""" self.reco_output = gr.Markdown(self._safe_get_recommendations()) gr.Markdown(""" ## 💡 Conseils pour la gestion des adventices ### Parcelles à Très Faible Risque (Vertes) - ✅ **Idéales pour pois et haricot** - ✅ Historique d'usage herbicide minimal - ✅ Pression adventice faible attendue ### Parcelles à Faible Risque (Vert clair) - ⚠️ Surveillance légère recommandée - ✅ Conviennent aux cultures sensibles avec précautions ### Parcelles à Risque Modéré/Élevé (Orange/Rouge) - ❌ Éviter pour cultures sensibles - 🔍 Rotation nécessaire avant implantation - 📈 Surveillance renforcée des adventices ### Stratégies alternatives - **Rotation longue**: 3-4 ans avant cultures sensibles - **Cultures intermédiaires**: CIPAN pour réduire la pression - **Techniques mécaniques**: Hersage, binage - **Biostimulants**: Renforcement naturel des cultures """) def _create_data_viewer_tab(self): """Crée l'onglet de visualisation des données par année""" gr.Markdown("## 📋 Exploration des Données par Année") # Récupérer les années disponibles available_years = self.visualizer.get_available_years() if not available_years: gr.Markdown("❌ Aucune année disponible dans les données") return # Ajouter une option "Toutes années" year_choices = ["Toutes les années"] + [str(year) for year in available_years] # Récupérer les parcelles disponibles available_parcels = self.analyzer.get_available_parcels() # Interface de sélection with gr.Row(): with gr.Column(scale=1): year_selector = gr.Dropdown( choices=year_choices, value="Toutes les années", label="🗓️ Sélectionnez une année", interactive=True ) parcel_selector = gr.Dropdown( choices=[choice[0] for choice in available_parcels], value="Toutes les parcelles", label="🏠 Sélectionnez une parcelle", interactive=True ) with gr.Column(scale=1): update_btn = gr.Button( "🔄 Actualiser la vue", variant="primary" ) # Informations sur la sélection self.data_info = gr.Markdown("📊 Sélectionnez une année pour voir les données") # Graphiques de résumé with gr.Row(): with gr.Column(): self.yearly_summary_plot = gr.Plot( self._safe_create_yearly_summary(None) ) with gr.Column(): self.monthly_activity_plot = gr.Plot( self._safe_create_monthly_activity(None) ) # Tableau des données gr.Markdown("### 📋 Tableau des Données") self.data_table = gr.Dataframe( value=self._safe_create_data_table(None)[0], label="Données de la période sélectionnée", interactive=False, wrap=True ) # Fonction de mise à jour des parcelles disponibles selon l'année def update_parcel_choices(selected_year): year = None if selected_year == "Toutes les années" else int(selected_year) parcels_for_year = self.analyzer.get_available_parcels_for_year(year) parcel_choices = [choice[0] for choice in parcels_for_year] return gr.Dropdown(choices=parcel_choices, value="Toutes les parcelles") # Fonction de mise à jour des années disponibles selon la parcelle def update_year_choices(selected_parcel): # Convertir la sélection de parcelle en ID parcel_id = None if selected_parcel != "Toutes les parcelles": current_parcels = self.analyzer.get_available_parcels() for choice in current_parcels: if choice[0] == selected_parcel: parcel_id = choice[1] if choice[1] != "ALL" else None break years_for_parcel = self.analyzer.get_available_years_for_parcel(parcel_id) return gr.Dropdown(choices=years_for_parcel, value="Toutes les années") # Fonction de mise à jour des données def update_data_view(selected_year, selected_parcel): # Convertir les sélections year = None if selected_year == "Toutes les années" else int(selected_year) # Convertir la sélection de parcelle parcel_id = None if selected_parcel != "Toutes les parcelles": # Récupérer les parcelles à jour pour éviter les problèmes de cache current_parcels = self.analyzer.get_available_parcels() # Récupérer l'ID de la parcelle depuis les données disponibles for choice in current_parcels: if choice[0] == selected_parcel: parcel_id = choice[1] if choice[1] != "ALL" else None break # Générer les nouvelles vues data_table, info_msg = self._safe_create_data_table(year, parcel_id) summary_plot = self._safe_create_yearly_summary(year, parcel_id) monthly_plot = self._safe_create_monthly_activity(year, parcel_id) return ( info_msg, summary_plot, monthly_plot, data_table ) # Connecter les événements pour les dropdowns dynamiques year_selector.change( update_parcel_choices, inputs=[year_selector], outputs=[parcel_selector] ).then( update_data_view, inputs=[year_selector, parcel_selector], outputs=[ self.data_info, self.yearly_summary_plot, self.monthly_activity_plot, self.data_table ] ) parcel_selector.change( update_year_choices, inputs=[parcel_selector], outputs=[year_selector] ).then( update_data_view, inputs=[year_selector, parcel_selector], outputs=[ self.data_info, self.yearly_summary_plot, self.monthly_activity_plot, self.data_table ] ) update_btn.click( update_data_view, inputs=[year_selector, parcel_selector], outputs=[ self.data_info, self.yearly_summary_plot, self.monthly_activity_plot, self.data_table ] ) gr.Markdown(""" ### 💡 Utilisation de l'onglet Données - **Sélection d'année** : Filtrez les données par millésime - **Sélection de parcelle** : Filtrez les données par parcelle spécifique - **Filtrage intelligent** : Les choix se mettent à jour automatiquement selon votre sélection - **Graphique des interventions** : Types d'interventions les plus fréquents - **Activité mensuelle** : Répartition des interventions par mois - **Tableau détaillé** : Données brutes avec colonnes importantes > 📝 **Note** : Le tableau est limité à 1000 lignes pour des raisons de performance > 🔄 **Astuce** : Les listes se mettent à jour dynamiquement - sélectionnez une année pour voir ses parcelles ! """) def _safe_create_data_table(self, year, parcel_id=None): """Crée le tableau de données""" if parcel_id is not None: # Utiliser la méthode qui prend en compte les parcelles return self.analyzer.get_data_table_by_year_and_parcel(year, parcel_id) else: return self.visualizer.create_data_table_by_year(year) def _safe_create_yearly_summary(self, year, parcel_id=None): """Crée le résumé annuel""" if parcel_id is not None: # Pour l'instant, utiliser la méthode existante car les méthodes du visualizer n'ont pas encore le support parcelle return self.visualizer.create_yearly_summary_chart(year) else: return self.visualizer.create_yearly_summary_chart(year) def _safe_create_monthly_activity(self, year, parcel_id=None): """Crée l'activité mensuelle""" if parcel_id is not None: # Pour l'instant, utiliser la méthode existante car les méthodes du visualizer n'ont pas encore le support parcelle return self.visualizer.create_monthly_activity_chart(year) else: return self.visualizer.create_monthly_activity_chart(year) def _create_about_tab(self): """Crée l'onglet à propos""" gr.Markdown(""" ## 🎯 Méthodologie Cette analyse se base sur : ### Calcul de l'IFT (Indice de Fréquence de Traitement) - **IFT ≈ Quantité appliquée / Surface de parcelle** - Indicateur de l'intensité des traitements herbicides ### Classification des risques - **TRÈS FAIBLE**: IFT = 0, aucun herbicide - **FAIBLE**: IFT < 1, usage minimal - **MODÉRÉ**: IFT < 3, usage modéré - **ÉLEVÉ**: IFT < 5, usage important - **TRÈS ÉLEVÉ**: IFT ≥ 5, usage intensif ### Données analysées - **Source**: Station Expérimentale de Kerguéhennec - **Période**: Campagne 2025 - **Variables**: Interventions, produits, quantités, surfaces --- **Développé pour le Hackathon CRA Bretagne** 🏆 *Application d'aide à la décision pour une agriculture durable* """) def launch(self): """Lance l'interface""" demo = self.create_interface() demo.launch(**GRADIO_CONFIG)