data / interface.py
Tracy André
updated
bc49a9e
"""
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)