Tracy André commited on
Commit
c2f1b4d
·
1 Parent(s): 2ce9eab
Files changed (3) hide show
  1. app.py +1 -1
  2. mcp/__init__.py +0 -8
  3. mcp/resources.py +0 -301
app.py CHANGED
@@ -12,4 +12,4 @@ if hf_token:
12
  os.environ["DATASET_ID"] = "HackathonCRA/2024"
13
 
14
  demo = create_mcp_interface()
15
- demo.launch(share=True, mcp_server=True)
 
12
  os.environ["DATASET_ID"] = "HackathonCRA/2024"
13
 
14
  demo = create_mcp_interface()
15
+ demo.launch(mcp_server=True)
mcp/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- """
2
- MCP Module for Agricultural Weed Pressure Analysis
3
- """
4
-
5
- from .tools import WeedPressureAnalyzer
6
- from .resources import AgriculturalResources
7
-
8
- __all__ = ['WeedPressureAnalyzer', 'AgriculturalResources']
 
 
 
 
 
 
 
 
 
mcp/resources.py DELETED
@@ -1,301 +0,0 @@
1
- import typing as t
2
- import pandas as pd
3
- import gradio as gr
4
-
5
- # -------------------------------------------------------------------
6
- # Hypothèse: AgriculturalDataLoader.load_all_files() concatène 10 CSV
7
- # et renvoie un DataFrame avec au moins ces colonnes (si présentes):
8
- # ["millesime","raisonsoci","siret","pacage","refca","numilot","numparcell",
9
- # "nomparc","surfparc","rang","kqte","teneurn","teneurp","teneurk",
10
- # "keq","volumebo","codeamm","codegnis","materiel","mainoeuvre", ...]
11
- # -------------------------------------------------------------------
12
-
13
- class AgriculturalResources:
14
- def __init__(self):
15
- self.data_loader = AgriculturalDataLoader()
16
- self.data_cache: t.Optional[pd.DataFrame] = None
17
-
18
- def load_data(self) -> pd.DataFrame:
19
- if self.data_cache is None:
20
- df = self.data_loader.load_all_files()
21
-
22
- # Normalisation minimale & robustesse
23
- df = df.copy()
24
- # Harmonise noms connus (au cas où)
25
- rename_map = {
26
- "raisonsoci": "raisonsociale",
27
- "numparcelle": "numparcell",
28
- "NomParc": "nomparc",
29
- "SurfParc": "surfparc",
30
- }
31
- for k, v in rename_map.items():
32
- if k in df.columns and v not in df.columns:
33
- df[v] = df[k]
34
-
35
- # Types & trim
36
- for col in ["millesime", "siret", "pacage", "refca", "numilot", "numparcell", "rang",
37
- "codeamm", "codegnis"]:
38
- if col in df.columns:
39
- df[col] = df[col].astype(str).str.strip()
40
-
41
- for col in ["nomparc", "raisonsociale", "materiel", "mainoeuvre"]:
42
- if col in df.columns:
43
- df[col] = df[col].astype(str).str.strip()
44
-
45
- for col in ["surfparc", "kqte", "teneurn", "teneurp", "teneurk", "keq", "volumebo"]:
46
- if col in df.columns:
47
- # coerce = NaN si non convertible
48
- df[col] = pd.to_numeric(df[col], errors="coerce")
49
-
50
- # IDs composites utiles
51
- if {"millesime", "numparcell"}.issubset(df.columns):
52
- df["parcelle_id"] = df["millesime"] + ":" + df["numparcell"]
53
- else:
54
- df["parcelle_id"] = None
55
-
56
- if {"millesime", "numparcell", "rang"}.issubset(df.columns):
57
- df["intervention_id"] = df["millesime"] + ":" + df["numparcell"] + ":" + df["rang"]
58
- else:
59
- df["intervention_id"] = None
60
-
61
- self.data_cache = df
62
-
63
- return self.data_cache
64
-
65
- # -------------------------
66
- # Utilitaires internes
67
- # -------------------------
68
- def _safe_first(self, df: pd.DataFrame) -> t.Optional[pd.Series]:
69
- if df is None or df.empty:
70
- return None
71
- return df.iloc[0]
72
-
73
- def _notnull(self, d: dict) -> dict:
74
- # Retire les champs None/NaN pour des payloads plus propres
75
- return {k: v for k, v in d.items() if pd.notna(v)}
76
-
77
- # -------------------------
78
- # LISTINGS / DISCOVERY
79
- # -------------------------
80
-
81
- @gr.mcp.resource("dataset://years")
82
- def list_years(self) -> t.List[str]:
83
- """Liste des millésimes disponibles dans l'ensemble des fichiers."""
84
- df = self.load_data()
85
- if "millesime" not in df.columns:
86
- return []
87
- years = sorted(df["millesime"].dropna().astype(str).unique())
88
- return years
89
-
90
- @gr.mcp.resource("exploitation://{siret}/parcelles")
91
- def list_parcelles_by_exploitation(self, siret: str, millesime: t.Optional[str] = None) -> t.List[dict]:
92
- """Liste les parcelles d'une exploitation (optionnellement filtrées par millésime)."""
93
- df = self.load_data()
94
- q = df[df["siret"] == siret] if "siret" in df.columns else df.iloc[0:0]
95
- if millesime:
96
- q = q[q["millesime"] == str(millesime)]
97
- cols = [c for c in ["parcelle_id","millesime","numparcell","nomparc","surfparc","refca","numilot"] if c in q.columns]
98
- out = q[cols].drop_duplicates().to_dict(orient="records")
99
- return out
100
-
101
- @gr.mcp.resource("parcelles://search")
102
- def search_parcelles(self, query: str = "", millesime: t.Optional[str] = None, limit: int = 50) -> t.List[dict]:
103
- """Recherche de parcelles par nom/numéro, filtrable par millésime."""
104
- df = self.load_data()
105
- q = df
106
- if millesime:
107
- q = q[q["millesime"] == str(millesime)]
108
- if query:
109
- mask = False
110
- if "numparcell" in q.columns:
111
- mask = q["numparcell"].str.contains(query, case=False, na=False)
112
- if "nomparc" in q.columns:
113
- mask = mask | q["nomparc"].str.contains(query, case=False, na=False)
114
- q = q[mask]
115
- cols = [c for c in ["parcelle_id","millesime","numparcell","nomparc","surfparc","refca","numilot","siret"] if c in q.columns]
116
- return q[cols].drop_duplicates().head(limit).to_dict(orient="records")
117
-
118
- # -------------------------
119
- # RESSOURCES CANONIQUES
120
- # -------------------------
121
-
122
- @gr.mcp.resource("exploitation://{siret}/{millesime}")
123
- def get_exploitation(self, siret: str, millesime: str) -> dict:
124
- """Infos d'une exploitation (pour un millésime donné)."""
125
- df = self.load_data()
126
- q = df[(df["siret"] == siret) & (df["millesime"] == str(millesime))] if {"siret","millesime"}.issubset(df.columns) else df.iloc[0:0]
127
- row = self._safe_first(q.sort_values(by=[c for c in ["millesime"] if c in q.columns], ascending=False))
128
- if row is None:
129
- return {}
130
- return self._notnull({
131
- "millesime": row.get("millesime"),
132
- "siret": row.get("siret"),
133
- "raison_sociale": row.get("raisonsociale"),
134
- "pacage": row.get("pacage"),
135
- })
136
-
137
- @gr.mcp.resource("parcelle://{millesime}/{numparcell}")
138
- def get_parcelle(self, millesime: str, numparcell: str) -> dict:
139
- """Infos d'une parcelle (identifiée par millésime + numparcell)."""
140
- df = self.load_data()
141
- q = df[(df["millesime"] == str(millesime)) & (df["numparcell"] == str(numparcell))]
142
- row = self._safe_first(q)
143
- if row is None:
144
- return {}
145
- return self._notnull({
146
- "parcelle_id": row.get("parcelle_id"),
147
- "millesime": row.get("millesime"),
148
- "numparcell": row.get("numparcell"),
149
- "nomparc": row.get("nomparc"),
150
- "surfparc": row.get("surfparc"),
151
- "siret": row.get("siret"),
152
- "refca": row.get("refca"),
153
- "numilot": row.get("numilot"),
154
- })
155
-
156
- @gr.mcp.resource("intervention://{millesime}/{numparcell}/{rang}")
157
- def get_intervention(self, millesime: str, numparcell: str, rang: str) -> dict:
158
- """Infos d'une intervention (clé composite millésime + numparcell + rang)."""
159
- df = self.load_data()
160
- q = df[(df["millesime"] == str(millesime)) & (df["numparcell"] == str(numparcell)) & (df["rang"] == str(rang))]
161
- row = self._safe_first(q)
162
- if row is None:
163
- return {}
164
- return self._notnull({
165
- "intervention_id": row.get("intervention_id"),
166
- "millesime": row.get("millesime"),
167
- "numparcell": row.get("numparcell"),
168
- "rang": row.get("rang"),
169
- "mainoeuvre": row.get("mainoeuvre"),
170
- "materiel": row.get("materiel"),
171
- "codeamm": row.get("codeamm"),
172
- "codegnis": row.get("codegnis"),
173
- "kqte": row.get("kqte"),
174
- "teneurn": row.get("teneurn"),
175
- "teneurp": row.get("teneurp"),
176
- "teneurk": row.get("teneurk"),
177
- "keq": row.get("keq"),
178
- "volumebo": row.get("volumebo"),
179
- })
180
-
181
- @gr.mcp.resource("intrant://{codeamm}")
182
- def get_intrant(self, codeamm: str, millesime: t.Optional[str] = None) -> dict:
183
- """Infos d’un intrant (filtrable par millésime)."""
184
- df = self.load_data()
185
- q = df[df["codeamm"] == str(codeamm)] if "codeamm" in df.columns else df.iloc[0:0]
186
- if millesime:
187
- q = q[q["millesime"] == str(millesime)]
188
- row = self._safe_first(q)
189
- if row is None:
190
- return {}
191
- return self._notnull({
192
- "codeamm": row.get("codeamm"),
193
- "codegnis": row.get("codegnis"),
194
- "millesime": row.get("millesime"),
195
- "kqte": row.get("kqte"),
196
- "teneurn": row.get("teneurn"),
197
- "teneurp": row.get("teneurp"),
198
- "teneurk": row.get("teneurk"),
199
- "keq": row.get("keq"),
200
- "volumebo": row.get("volumebo"),
201
- })
202
-
203
- @gr.mcp.resource("materiel://{millesime}/{numparcell}/{rang}")
204
- def get_materiel(self, millesime: str, numparcell: str, rang: str) -> dict:
205
- """Matériel utilisé pour une intervention donnée."""
206
- df = self.load_data()
207
- q = df[(df["millesime"] == str(millesime)) & (df["numparcell"] == str(numparcell)) & (df["rang"] == str(rang))]
208
- row = self._safe_first(q)
209
- if row is None:
210
- return {}
211
- return self._notnull({
212
- "millesime": row.get("millesime"),
213
- "numparcell": row.get("numparcell"),
214
- "rang": row.get("rang"),
215
- "materiel": row.get("materiel"),
216
- })
217
-
218
- @gr.mcp.resource("maindoeuvre://{millesime}/{numparcell}/{rang}")
219
- def get_main_oeuvre(self, millesime: str, numparcell: str, rang: str) -> dict:
220
- """Main d’œuvre associée à une intervention donnée."""
221
- df = self.load_data()
222
- q = df[(df["millesime"] == str(millesime)) & (df["numparcell"] == str(numparcell)) & (df["rang"] == str(rang))]
223
- row = self._safe_first(q)
224
- if row is None:
225
- return {}
226
- return self._notnull({
227
- "millesime": row.get("millesime"),
228
- "numparcell": row.get("numparcell"),
229
- "rang": row.get("rang"),
230
- "mainoeuvre": row.get("mainoeuvre"),
231
- })
232
-
233
- # -------------------------------------------------------------------
234
- # Gradio: interfaces simples de test (onglets)
235
- # -------------------------------------------------------------------
236
-
237
- res = AgriculturalResources()
238
-
239
- demo = gr.TabbedInterface(
240
- [
241
- # Data discovery
242
- gr.Interface(res.list_years, inputs=[], outputs=gr.JSON(), title="Years"),
243
- gr.Interface(res.list_parcelles_by_exploitation,
244
- inputs=[gr.Textbox(value="18560001000016", label="SIRET"),
245
- gr.Textbox(value="", label="Millesime (optionnel)")],
246
- outputs=gr.JSON(),
247
- title="Parcelles par exploitation"),
248
- gr.Interface(res.search_parcelles,
249
- inputs=[gr.Textbox(value="", label="Query (num/nom parcelle)"),
250
- gr.Textbox(value="", label="Millesime (optionnel)"),
251
- gr.Number(value=50, label="Limit")],
252
- outputs=gr.JSON(),
253
- title="Recherche parcelles"),
254
-
255
- # Resources canoniques
256
- gr.Interface(res.get_exploitation,
257
- inputs=[gr.Textbox(value="18560001000016", label="SIRET"),
258
- gr.Textbox(value="2025", label="Millesime")],
259
- outputs=gr.JSON(),
260
- title="Exploitation"),
261
- gr.Interface(res.get_parcelle,
262
- inputs=[gr.Textbox(value="2025", label="Millesime"),
263
- gr.Textbox(value="12", label="Num parcelle")],
264
- outputs=gr.JSON(),
265
- title="Parcelle"),
266
- gr.Interface(res.get_intervention,
267
- inputs=[gr.Textbox(value="2025", label="Millesime"),
268
- gr.Textbox(value="12", label="Num parcelle"),
269
- gr.Textbox(value="1", label="Rang")],
270
- outputs=gr.JSON(),
271
- title="Intervention"),
272
- gr.Interface(res.get_intrant,
273
- inputs=[gr.Textbox(value="9100296", label="Code AMM"),
274
- gr.Textbox(value="", label="Millesime (optionnel)")],
275
- outputs=gr.JSON(),
276
- title="Intrant"),
277
- gr.Interface(res.get_materiel,
278
- inputs=[gr.Textbox(value="2025", label="Millesime"),
279
- gr.Textbox(value="12", label="Num parcelle"),
280
- gr.Textbox(value="1", label="Rang")],
281
- outputs=gr.JSON(),
282
- title="Matériel"),
283
- gr.Interface(res.get_main_oeuvre,
284
- inputs=[gr.Textbox(value="2025", label="Millesime"),
285
- gr.Textbox(value="12", label="Num parcelle"),
286
- gr.Textbox(value="1", label="Rang")],
287
- outputs=gr.JSON(),
288
- title="Main d'œuvre"),
289
- ],
290
- [
291
- "Years",
292
- "Parcelles par Exploitation",
293
- "Recherche Parcelles",
294
- "Exploitation",
295
- "Parcelle",
296
- "Intervention",
297
- "Intrant",
298
- "Matériel",
299
- "Main d'œuvre",
300
- ]
301
- )