Eddyhzd commited on
Commit
e360f54
·
1 Parent(s): ccce7c7
Files changed (3) hide show
  1. app.py +31 -84
  2. data_loader.py +0 -269
  3. mcp_server.py +0 -302
app.py CHANGED
@@ -1,90 +1,37 @@
1
- import gradio as gr
2
- from openai import OpenAI
3
- import os
4
- from mcp import ClientSession, StdioServerParameters
5
- from mcp.client.stdio import stdio_client
6
  import asyncio
7
- from contextlib import AsyncExitStack
8
-
9
- cle_api = os.environ.get("CLE_API_MISTRAL")
10
-
11
- # Initialisation du client Mistral (API compatible OpenAI)
12
- client = OpenAI(api_key=cle_api, base_url="https://api.mistral.ai/v1")
13
-
14
- # Chatbot : simple écho Fonction chatbot reliée à Mistral
15
- def chatbot(message, history):
16
- # Préparer l’historique dans le format de Mistral
17
- messages = []
18
- for user_msg, bot_msg in history:
19
- messages.append({"role": "user", "content": user_msg})
20
- messages.append({"role": "assistant", "content": bot_msg})
21
-
22
- messages.append({"role": "user", "content": message})
23
-
24
- # Appel API Mistral
25
- response = client.chat.completions.create(
26
- model="mistral-small-latest",
27
- messages=messages
28
- )
29
-
30
- bot_reply = response.choices[0].message.content.strip()
31
- history.append(("Vous: " + message, "Bot: " + bot_reply))
32
- return history, history
33
-
34
-
35
- loop = asyncio.new_event_loop()
36
- asyncio.set_event_loop(loop)
37
-
38
- class MCPClientWrapper:
39
- def __init__(self):
40
- self.session = None
41
- self.exit_stack = None
42
- self.tools = []
43
-
44
- def connect(self, server_path: str) -> str:
45
- return loop.run_until_complete(self._connect(server_path))
46
-
47
- async def _connect(self, server_path: str) -> str:
48
- if self.exit_stack:
49
- await self.exit_stack.aclose()
50
-
51
- self.exit_stack = AsyncExitStack()
52
-
53
- is_python = server_path.endswith('.py')
54
- command = "python" if is_python else "node"
55
-
56
- server_params = StdioServerParameters(
57
- command=command,
58
- args=[server_path],
59
- env={"PYTHONIOENCODING": "utf-8", "PYTHONUNBUFFERED": "1"}
60
  )
61
-
62
- stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
63
- self.stdio, self.write = stdio_transport
64
-
65
- self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
66
- await self.session.initialize()
67
-
68
- response = await self.session.list_tools()
69
- self.tools = [{
70
- "name": tool.name,
71
- "description": tool.description,
72
- "input_schema": tool.inputSchema
73
- } for tool in response.tools]
74
-
75
- tool_names = [tool["name"] for tool in self.tools]
76
- return f"Connected to MCP server. Available tools: {', '.join(tool_names)}"
77
 
78
- client = MCPClientWrapper()
79
-
80
- with gr.Blocks() as demo:
81
-
82
- client.connect("mcp_server.py")
83
- print(f"Connected to MCP server. Available tools: {', '.join([tool['name'] for tool in client.tools])}")
84
 
85
- chatbot_ui = gr.Chatbot(label="ChatBot")
86
- msg = gr.Textbox(placeholder="Écrivez un message...")
 
 
 
 
87
 
88
- msg.submit(chatbot, [msg, chatbot_ui], [chatbot_ui, chatbot_ui])
89
 
90
- demo.launch(debug=True)
 
 
 
 
 
 
 
1
  import asyncio
2
+ import gradio as gr
3
+ from mcp import ClientSession
4
+
5
+ # Exemple de config MCP
6
+ MCP_SERVERS = {
7
+ "gradio": {
8
+ "url": "https://hackathoncra-gradio-mcp.hf.space/gradio_api/mcp/"
9
+ }
10
+ }
11
+
12
+ async def query_mcp(prompt: str):
13
+ # Crée une session MCP (ici en HTTP)
14
+ async with ClientSession("https://hackathoncra-gradio-mcp.hf.space/gradio_api/mcp/") as session:
15
+ # Exécute une requête MCP (par exemple un appel "text-generation")
16
+ print(session)
17
+ response = await session.call(
18
+ method="text-generation", # dépend des méthodes exposées par ton serveur
19
+ params={"prompt": prompt}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  )
21
+ return response.get("result", "Pas de résultat MCP")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
+ # Fonction Gradio qui appelle MCP
24
+ def chat_with_mcp(prompt):
25
+ return asyncio.run(query_mcp(prompt))
 
 
 
26
 
27
+ # Interface Gradio
28
+ with gr.Blocks() as demo:
29
+ gr.Markdown("# 🚀 Client MCP avec Gradio")
30
+ inp = gr.Textbox(label="Votre prompt")
31
+ out = gr.Textbox(label="Réponse du MCP")
32
+ btn = gr.Button("Envoyer")
33
 
34
+ btn.click(chat_with_mcp, inputs=inp, outputs=out)
35
 
36
+ if __name__ == "__main__":
37
+ demo.launch()
data_loader.py DELETED
@@ -1,269 +0,0 @@
1
- """
2
- Data loader for agricultural intervention data.
3
- Loads data exclusively from Hugging Face datasets.
4
- """
5
-
6
- import pandas as pd
7
- import numpy as np
8
- from typing import List, Optional
9
- import os
10
- from datasets import Dataset, load_dataset
11
- from huggingface_hub import HfApi, hf_hub_download
12
-
13
-
14
- class AgriculturalDataLoader:
15
- """Loads and preprocesses agricultural intervention data from Hugging Face datasets."""
16
-
17
- def __init__(self, hf_token: str = None, dataset_id: str = None):
18
- self.hf_token = hf_token or os.environ.get("HF_TOKEN")
19
- self.dataset_id = dataset_id or "HackathonCRA/2024"
20
- self.data_cache = {}
21
-
22
- def load_all_files(self) -> pd.DataFrame:
23
- """Load data from Hugging Face dataset."""
24
- if 'combined_data' in self.data_cache:
25
- return self.data_cache['combined_data']
26
-
27
- # Load from Hugging Face only
28
- df = self.load_from_huggingface()
29
- self.data_cache['combined_data'] = df
30
- return df
31
-
32
- def load_from_huggingface(self) -> pd.DataFrame:
33
- """Load data from Hugging Face dataset."""
34
- print(f"🤗 Loading dataset from Hugging Face: {self.dataset_id}")
35
-
36
- try:
37
- # Try multiple loading strategies
38
- df = None
39
-
40
- # Strategy 1: Try direct dataset loading
41
- try:
42
- dataset = load_dataset(
43
- self.dataset_id,
44
- token=self.hf_token,
45
- streaming=False
46
- )
47
- df = dataset["train"].to_pandas()
48
- print(f"✅ Loaded via load_dataset: {len(df)} records")
49
-
50
- except Exception as e1:
51
- print(f"⚠️ load_dataset failed: {e1}")
52
-
53
- # Strategy 2: Load individual CSV files from HF Hub
54
- try:
55
- df = self._load_csv_files_from_hub()
56
- print(f"✅ Loaded via individual CSV files: {len(df)} records")
57
-
58
- except Exception as e2:
59
- print(f"⚠️ CSV loading failed: {e2}")
60
- raise ValueError(f"All loading strategies failed. Dataset: {e1}, CSV: {e2}")
61
-
62
- if df is None or len(df) == 0:
63
- raise ValueError("No data loaded from any strategy")
64
-
65
- # Apply preprocessing
66
- df = self._preprocess_data(df)
67
- print(f"✅ Successfully processed {len(df)} records from Hugging Face")
68
-
69
- return df
70
-
71
- except Exception as e:
72
- raise ValueError(f"Failed to load dataset from Hugging Face: {e}")
73
-
74
- def _load_csv_files_from_hub(self) -> pd.DataFrame:
75
- """Load individual CSV files from Hugging Face Hub."""
76
- from huggingface_hub import hf_hub_download
77
- import tempfile
78
-
79
- print("📂 Loading individual CSV files from HF Hub...")
80
-
81
- # Get list of CSV files
82
- api = HfApi()
83
- try:
84
- repo_info = api.repo_info(repo_id=self.dataset_id, repo_type="dataset", token=self.hf_token)
85
- csv_files = [f.rfilename for f in repo_info.siblings if f.rfilename.endswith('.csv')]
86
- except Exception as e:
87
- raise ValueError(f"Failed to get repo info: {e}")
88
-
89
- if not csv_files:
90
- raise ValueError("No CSV files found in the dataset repository")
91
-
92
- print(f"📋 Found {len(csv_files)} CSV files")
93
-
94
- all_dataframes = []
95
-
96
- for csv_file in csv_files:
97
- try:
98
- # Download CSV file to temporary location
99
- local_path = hf_hub_download(
100
- repo_id=self.dataset_id,
101
- filename=csv_file,
102
- repo_type="dataset",
103
- token=self.hf_token
104
- )
105
-
106
- # Read CSV with appropriate settings
107
- # First, let's check if we need to skip the first row
108
- df = pd.read_csv(local_path)
109
-
110
- # If the first row contains "Interventions (sortie sous excel)", skip it
111
- if df.columns[0].startswith('Interventions'):
112
- df = pd.read_csv(local_path)
113
- all_dataframes.append(df)
114
- print(f" ✅ {csv_file}: {len(df)} rows")
115
-
116
- except Exception as e:
117
- print(f" ⚠️ Failed to load {csv_file}: {e}")
118
- continue
119
-
120
- if not all_dataframes:
121
- raise ValueError("No CSV files could be loaded successfully")
122
-
123
- # Combine all dataframes
124
- combined_df = pd.concat(all_dataframes, ignore_index=True)
125
- return combined_df
126
-
127
- def _preprocess_data(self, df: pd.DataFrame) -> pd.DataFrame:
128
- """Preprocess the agricultural data."""
129
- print(f"🔧 Preprocessing {len(df)} records...")
130
- print(f"📋 Available columns: {list(df.columns)}")
131
-
132
- # Convert date columns
133
- date_columns = ['datedebut', 'datefin']
134
- for col in date_columns:
135
- if col in df.columns:
136
- df[col] = pd.to_datetime(df[col], format='%d/%m/%y', errors='coerce')
137
-
138
- # Convert numeric columns
139
- numeric_columns = ['surfparc', 'quantitetot', 'neffqte', 'peffqte', 'kqte',
140
- 'teneurn', 'teneurp', 'teneurk', 'keq', 'volumebo']
141
- for col in numeric_columns:
142
- if col in df.columns:
143
- df[col] = pd.to_numeric(df[col], errors='coerce')
144
-
145
- # Add derived columns (with error checking)
146
- if 'millesime' in df.columns:
147
- df['year'] = df['millesime']
148
- else:
149
- print("⚠️ Column 'millesime' not found, trying to infer year from filename or date")
150
- # Try to extract year from date if available
151
- if 'datedebut' in df.columns:
152
- df['year'] = pd.to_datetime(df['datedebut'], errors='coerce').dt.year
153
- else:
154
- # Set a default year or raise error
155
- print("❌ Cannot determine year - setting to 2024 as default")
156
- df['year'] = 2024
157
-
158
- if 'libelleusag' in df.columns:
159
- df['crop_type'] = df['libelleusag']
160
- else:
161
- df['crop_type'] = 'unknown'
162
-
163
- if 'libevenem' in df.columns:
164
- df['intervention_type'] = df['libevenem']
165
- else:
166
- df['intervention_type'] = 'unknown'
167
-
168
- if 'familleprod' in df.columns:
169
- df['product_family'] = df['familleprod']
170
- # Calculate IFT (Treatment Frequency Index) for herbicides
171
- df['is_herbicide'] = df['familleprod'].str.contains('Herbicides', na=False)
172
- df['is_fungicide'] = df['familleprod'].str.contains('Fongicides', na=False)
173
- df['is_insecticide'] = df['familleprod'].str.contains('Insecticides', na=False)
174
- else:
175
- df['product_family'] = 'unknown'
176
- df['is_herbicide'] = False
177
- df['is_fungicide'] = False
178
- df['is_insecticide'] = False
179
-
180
- if 'nomparc' in df.columns:
181
- df['plot_name'] = df['nomparc']
182
- else:
183
- df['plot_name'] = 'unknown'
184
-
185
- if 'numparcell' in df.columns:
186
- df['plot_number'] = df['numparcell']
187
- else:
188
- df['plot_number'] = 0
189
-
190
- if 'surfparc' in df.columns:
191
- df['plot_surface'] = df['surfparc']
192
- else:
193
- df['plot_surface'] = 1.0
194
-
195
- print(f"✅ Preprocessing completed: {len(df)} records with {len(df.columns)} columns")
196
- return df
197
-
198
- def get_years_available(self) -> List[int]:
199
- """Get list of available years in the data."""
200
- df = self.load_all_files()
201
- return sorted(df['year'].dropna().unique().astype(int).tolist())
202
-
203
- def get_plots_available(self) -> List[str]:
204
- """Get list of available plots."""
205
- df = self.load_all_files()
206
- return sorted(df['plot_name'].dropna().unique().tolist())
207
-
208
- def get_crops_available(self) -> List[str]:
209
- """Get list of available crop types."""
210
- df = self.load_all_files()
211
- return sorted(df['crop_type'].dropna().unique().tolist())
212
-
213
- def filter_data(self,
214
- years: Optional[List[int]] = None,
215
- plots: Optional[List[str]] = None,
216
- crops: Optional[List[str]] = None,
217
- intervention_types: Optional[List[str]] = None) -> pd.DataFrame:
218
- """Filter the data based on criteria."""
219
- df = self.load_all_files()
220
-
221
- if years:
222
- df = df[df['year'].isin(years)]
223
- if plots:
224
- df = df[df['plot_name'].isin(plots)]
225
- if crops:
226
- df = df[df['crop_type'].isin(crops)]
227
- if intervention_types:
228
- df = df[df['intervention_type'].isin(intervention_types)]
229
-
230
- return df
231
-
232
- def get_herbicide_usage(self, years: Optional[List[int]] = None) -> pd.DataFrame:
233
- """Get herbicide usage data for weed pressure analysis."""
234
- df = self.filter_data(years=years)
235
- herbicide_data = df[df['is_herbicide'] == True].copy()
236
-
237
- # Group by plot, year, and crop
238
- usage_summary = herbicide_data.groupby(['plot_name', 'year', 'crop_type']).agg({
239
- 'quantitetot': 'sum',
240
- 'produit': 'count', # Number of herbicide applications
241
- 'surfparc': 'first'
242
- }).reset_index()
243
-
244
- usage_summary.columns = ['plot_name', 'year', 'crop_type', 'total_quantity', 'num_applications', 'plot_surface']
245
- usage_summary['ift_herbicide'] = usage_summary['num_applications'] / usage_summary['plot_surface']
246
-
247
- return usage_summary
248
-
249
- def upload_to_huggingface(self) -> str:
250
- """Upload data to Hugging Face dataset."""
251
- if not self.hf_token:
252
- raise ValueError("HF_TOKEN not provided")
253
-
254
- df = self.load_all_files()
255
- dataset = Dataset.from_pandas(df)
256
-
257
- # Upload to Hugging Face
258
- dataset.push_to_hub(
259
- repo_id=self.dataset_id,
260
- token=self.hf_token,
261
- private=False
262
- )
263
-
264
- return f"Data uploaded to {self.dataset_id}"
265
-
266
- def clear_cache(self):
267
- """Clear cached data to force reload from Hugging Face."""
268
- self.data_cache.clear()
269
- print("📋 Cache cleared - will reload from Hugging Face on next access")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mcp_server.py DELETED
@@ -1,302 +0,0 @@
1
- """MCP Server for Agricultural Weed Pressure Analysis"""
2
-
3
- import gradio as gr
4
- import pandas as pd
5
- import numpy as np
6
- import plotly.express as px
7
- from data_loader import AgriculturalDataLoader
8
- import warnings
9
- from mcp.server.fastmcp import FastMCP
10
- warnings.filterwarnings('ignore')
11
-
12
- mcp = FastMCP("mcp")
13
-
14
- class WeedPressureAnalyzer:
15
- """Analyze weed pressure and recommend plots for sensitive crops."""
16
-
17
- def __init__(self):
18
- self.data_loader = AgriculturalDataLoader()
19
- self.data_cache = None
20
-
21
- def load_data(self):
22
- if self.data_cache is None:
23
- self.data_cache = self.data_loader.load_all_files()
24
- return self.data_cache
25
-
26
- def calculate_herbicide_ift(self, years=None):
27
- """Calculate IFT for herbicides by plot and year."""
28
- df = self.load_data()
29
-
30
- if years:
31
- df = df[df['year'].isin(years)]
32
-
33
- herbicide_df = df[df['is_herbicide'] == True].copy()
34
-
35
- if len(herbicide_df) == 0:
36
- return pd.DataFrame()
37
-
38
- ift_summary = herbicide_df.groupby(['plot_name', 'year', 'crop_type']).agg({
39
- 'produit': 'count',
40
- 'plot_surface': 'first',
41
- 'quantitetot': 'sum'
42
- }).reset_index()
43
-
44
- ift_summary['ift_herbicide'] = ift_summary['produit'] / ift_summary['plot_surface']
45
-
46
- return ift_summary
47
-
48
- def predict_weed_pressure(self, target_years=[2025, 2026, 2027]):
49
- """Predict weed pressure for future years."""
50
- ift_data = self.calculate_herbicide_ift()
51
-
52
- if len(ift_data) == 0:
53
- return pd.DataFrame()
54
-
55
- predictions = []
56
-
57
- for plot in ift_data['plot_name'].unique():
58
- plot_data = ift_data[ift_data['plot_name'] == plot].sort_values('year')
59
-
60
- if len(plot_data) < 2:
61
- continue
62
-
63
- years = plot_data['year'].values
64
- ift_values = plot_data['ift_herbicide'].values
65
-
66
- if len(years) > 1:
67
- slope = np.polyfit(years, ift_values, 1)[0]
68
- intercept = np.polyfit(years, ift_values, 1)[1]
69
-
70
- for target_year in target_years:
71
- predicted_ift = slope * target_year + intercept
72
- predicted_ift = max(0, predicted_ift)
73
-
74
- if predicted_ift < 1.0:
75
- risk_level = "Faible"
76
- elif predicted_ift < 2.0:
77
- risk_level = "Modéré"
78
- else:
79
- risk_level = "Élevé"
80
-
81
- predictions.append({
82
- 'plot_name': plot,
83
- 'year': target_year,
84
- 'predicted_ift': predicted_ift,
85
- 'risk_level': risk_level,
86
- 'recent_crops': ', '.join(plot_data['crop_type'].tail(3).unique()),
87
- 'historical_avg_ift': plot_data['ift_herbicide'].mean()
88
- })
89
-
90
- return pd.DataFrame(predictions)
91
-
92
- # Initialize analyzer
93
- analyzer = WeedPressureAnalyzer()
94
-
95
- @mcp.tool()
96
- def analyze_herbicide_trends(years_range, plot_filter):
97
- """Analyze herbicide usage trends over time."""
98
- try:
99
- if len(years_range) == 2:
100
- years = list(range(int(years_range[0]), int(years_range[1]) + 1))
101
- else:
102
- years = [int(y) for y in years_range]
103
-
104
- ift_data = analyzer.calculate_herbicide_ift(years=years)
105
-
106
- if len(ift_data) == 0:
107
- return None, "Aucune donnée d'herbicides trouvée."
108
-
109
- if plot_filter != "Toutes":
110
- ift_data = ift_data[ift_data['plot_name'] == plot_filter]
111
-
112
- fig = px.line(ift_data,
113
- x='year',
114
- y='ift_herbicide',
115
- color='plot_name',
116
- title=f'Évolution de l\'IFT Herbicides',
117
- labels={'ift_herbicide': 'IFT Herbicides', 'year': 'Année'})
118
-
119
- summary = f"""
120
- 📊 **Analyse de l'IFT Herbicides**
121
-
122
- **Statistiques:**
123
- - IFT moyen: {ift_data['ift_herbicide'].mean():.2f}
124
- - IFT maximum: {ift_data['ift_herbicide'].max():.2f}
125
- - Nombre de parcelles: {ift_data['plot_name'].nunique()}
126
-
127
- **Interprétation:**
128
- - IFT < 1.0: Pression faible ✅
129
- - IFT 1.0-2.0: Pression modérée ⚠️
130
- - IFT > 2.0: Pression élevée ❌
131
- """
132
-
133
- return fig, summary
134
-
135
- except Exception as e:
136
- return None, f"Erreur: {str(e)}"
137
-
138
- @mcp.tool()
139
- def predict_future_weed_pressure():
140
- """Predict weed pressure for the next 3 years."""
141
- try:
142
- predictions = analyzer.predict_weed_pressure()
143
-
144
- if len(predictions) == 0:
145
- return None, "Impossible de générer des prédictions."
146
-
147
- fig = px.bar(predictions,
148
- x='plot_name',
149
- y='predicted_ift',
150
- color='risk_level',
151
- facet_col='year',
152
- title='Prédiction Pression Adventices (2025-2027)',
153
- color_discrete_map={'Faible': 'green', 'Modéré': 'orange', 'Élevé': 'red'})
154
-
155
- low_risk = len(predictions[predictions['risk_level'] == 'Faible'])
156
- moderate_risk = len(predictions[predictions['risk_level'] == 'Modéré'])
157
- high_risk = len(predictions[predictions['risk_level'] == 'Élevé'])
158
-
159
- summary = f"""
160
- 🔮 **Prédictions 2025-2027**
161
-
162
- **Répartition des risques:**
163
- - ✅ Risque faible: {low_risk} prédictions
164
- - ⚠️ Risque modéré: {moderate_risk} prédictions
165
- - ❌ Risque élevé: {high_risk} prédictions
166
- """
167
-
168
- return fig, summary
169
-
170
- except Exception as e:
171
- return None, f"Erreur: {str(e)}"
172
-
173
- @mcp.tool()
174
- def recommend_sensitive_crop_plots():
175
- """Recommend plots for sensitive crops."""
176
- try:
177
- predictions = analyzer.predict_weed_pressure()
178
-
179
- if len(predictions) == 0:
180
- return None, "Aucune recommandation disponible."
181
-
182
- suitable_plots = predictions[predictions['risk_level'] == "Faible"].copy()
183
-
184
- if len(suitable_plots) > 0:
185
- suitable_plots['recommendation_score'] = 100 - (suitable_plots['predicted_ift'] * 30)
186
- suitable_plots = suitable_plots.sort_values('recommendation_score', ascending=False)
187
-
188
- top_recommendations = suitable_plots.head(10)[['plot_name', 'year', 'predicted_ift', 'recommendation_score']]
189
-
190
- summary = f"""
191
- 🌱 **Recommandations Cultures Sensibles**
192
-
193
- **Top parcelles recommandées:**
194
- {top_recommendations.to_string(index=False)}
195
-
196
- **Critères:** IFT prédit < 1.0 (faible pression adventices)
197
- """
198
-
199
- fig = px.scatter(suitable_plots,
200
- x='predicted_ift',
201
- y='recommendation_score',
202
- color='year',
203
- hover_data=['plot_name'],
204
- title='Parcelles Recommandées pour Cultures Sensibles')
205
-
206
- return fig, summary
207
- else:
208
- return None, "Aucune parcelle à faible risque identifiée."
209
-
210
- except Exception as e:
211
- return None, f"Erreur: {str(e)}"
212
-
213
- @mcp.tool()
214
- def generate_technical_alternatives(herbicide_family):
215
- """Generate technical alternatives."""
216
- summary = f"""
217
- 🔄 **Alternatives aux {herbicide_family}**
218
-
219
- **🚜 Alternatives Mécaniques:**
220
- • Faux-semis répétés avant implantation
221
- • Binage mécanique en inter-rang
222
- • Herse étrille en post-levée précoce
223
-
224
- **🌾 Alternatives Culturales:**
225
- • Rotation longue avec prairie temporaire
226
- • Cultures intermédiaires piège à nitrates
227
- • Densité de semis optimisée
228
-
229
- **🧪 Alternatives Biologiques:**
230
- • Stimulateurs de défenses naturelles
231
- • Extraits végétaux (huiles essentielles)
232
- • Bioherbicides à base de champignons
233
-
234
- **📋 Plan d'Action:**
235
- 1. Tester sur petites surfaces
236
- 2. Former les équipes
237
- 3. Suivre l'efficacité
238
- 4. Documenter les résultats
239
- """
240
-
241
- return summary
242
-
243
- def get_available_plots():
244
- """Get available plots."""
245
- try:
246
- plots = analyzer.data_loader.get_plots_available()
247
- return ["Toutes"] + plots
248
- except:
249
- return ["Toutes"]
250
-
251
- # Create Gradio Interface
252
- def create_mcp_interface():
253
- with gr.Blocks(title="🚜 Analyse Pression Adventices", theme=gr.themes.Soft()) as demo:
254
- gr.Markdown("""
255
- # 🚜 Analyse Pression Adventices - CRA Bretagne
256
-
257
- Anticiper et réduire la pression des adventices pour optimiser les cultures sensibles (pois, haricot).
258
- """)
259
-
260
- with gr.Tabs():
261
- with gr.Tab("📈 Analyse Tendances"):
262
- with gr.Row():
263
- years_slider = gr.Slider(2014, 2024, value=[2020, 2024], step=1, label="Période")
264
- plot_dropdown = gr.Dropdown(choices=get_available_plots(), value="Toutes", label="Parcelle")
265
-
266
- analyze_btn = gr.Button("🔍 Analyser", variant="primary")
267
-
268
- with gr.Row():
269
- trends_plot = gr.Plot()
270
- trends_summary = gr.Markdown()
271
-
272
- analyze_btn.click(analyze_herbicide_trends, [years_slider, plot_dropdown], [trends_plot, trends_summary])
273
-
274
- with gr.Tab("🔮 Prédictions"):
275
- predict_btn = gr.Button("🎯 Prédire 2025-2027", variant="primary")
276
-
277
- with gr.Row():
278
- predictions_plot = gr.Plot()
279
- predictions_summary = gr.Markdown()
280
-
281
- predict_btn.click(predict_future_weed_pressure, outputs=[predictions_plot, predictions_summary])
282
-
283
- with gr.Tab("🌱 Recommandations"):
284
- recommend_btn = gr.Button("🎯 Recommander Parcelles", variant="primary")
285
-
286
- with gr.Row():
287
- recommendations_plot = gr.Plot()
288
- recommendations_summary = gr.Markdown()
289
-
290
- recommend_btn.click(recommend_sensitive_crop_plots, outputs=[recommendations_plot, recommendations_summary])
291
-
292
- with gr.Tab("🔄 Alternatives"):
293
- herbicide_type = gr.Dropdown(["Herbicides", "Fongicides"], value="Herbicides", label="Type")
294
- alternatives_btn = gr.Button("💡 Générer Alternatives", variant="primary")
295
- alternatives_output = gr.Markdown()
296
-
297
- alternatives_btn.click(generate_technical_alternatives, [herbicide_type], [alternatives_output])
298
-
299
- return demo
300
-
301
- if __name__ == "__main__":
302
- mcp.run(transport='stdio')