MMOON commited on
Commit
3a614e7
·
verified ·
1 Parent(s): 338d802

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -110
app.py CHANGED
@@ -4,8 +4,7 @@ from datetime import datetime, timedelta
4
  from typing import Dict, List, Optional
5
  import gradio as gr
6
  import pandas as pd
7
- import aiohttp
8
- import asyncio
9
  from dataclasses import dataclass
10
  from tenacity import retry, stop_after_attempt, wait_fixed
11
  import plotly.express as px
@@ -45,32 +44,37 @@ class PesticideDataFetcher:
45
 
46
  def __init__(self):
47
  self.session = self._create_session()
 
 
48
 
49
  def _create_session(self):
50
  """Crée une session pour les requêtes HTTP."""
51
- return aiohttp.ClientSession(headers=self.HEADERS)
 
 
 
52
 
53
  @retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
54
- async def fetch_data(self, url: str) -> Dict:
55
  """Récupère les données depuis l'API avec gestion d'erreurs et retry."""
56
  try:
57
- async with self.session.get(url, timeout=10) as response:
58
- response.raise_for_status()
59
- data = await response.json()
60
- logger.info(f"Fetched data from {url}: {str(data)[:200]}...")
61
- return data
62
- except aiohttp.ClientError as e:
63
  logger.error(f"Failed to fetch data from {url}: {str(e)}")
64
  return {"error": str(e)}
65
 
66
- async def get_active_substance_details(self, substance_name: str) -> Dict:
67
  """Récupère les détails d'une substance active."""
68
  cache_key = f"substance:{substance_name}"
69
  cached_data = redis_client.get(cache_key)
70
  if cached_data:
71
  return eval(cached_data)
72
  url = f"{self.BASE_URL}/active_substances?format=json&substance_name={substance_name}&api-version=v2.0"
73
- response = await self.fetch_data(url)
74
  if response and "value" in response and response["value"]:
75
  substance_data = response["value"][0]
76
  substance_details = {
@@ -82,7 +86,7 @@ class PesticideDataFetcher:
82
  return substance_details
83
  return {}
84
 
85
- async def get_products(self) -> List[Dict]:
86
  """Récupère la liste complète des produits avec pagination."""
87
  cache_key = "products"
88
  cached_data = redis_client.get(cache_key)
@@ -92,7 +96,7 @@ class PesticideDataFetcher:
92
  base_url = f"{self.BASE_URL}/pesticide_residues_products?format=json&language=FR&api-version=v2.0"
93
  url = base_url
94
  while url:
95
- response = await self.fetch_data(url)
96
  if not response or "value" not in response:
97
  break
98
  all_products.extend(response["value"])
@@ -105,25 +109,16 @@ class PesticideDataFetcher:
105
  logger.info(f"Récupéré {len(all_products)} produits au total")
106
  return all_products
107
 
108
- async def get_mrls(self, product_id: int) -> List[Dict]:
109
  """Récupère les LMR pour un produit spécifique."""
110
- cache_key = f"mrls:{product_id}"
111
- cached_data = redis_client.get(cache_key)
112
- if cached_data:
113
- return eval(cached_data)
114
  url = f"{self.BASE_URL}/pesticide_residues_mrls?format=json&product_id={product_id}&api-version=v2.0"
115
- response = await self.fetch_data(url)
116
- redis_client.set(cache_key, str(response.get("value", [])), ex=3600)
117
  return response.get("value", [])
118
 
119
- async def get_substance_details(self, pesticide_residue_id: int) -> Dict:
120
  """Récupère les détails d'une substance à partir de son ID."""
121
- cache_key = f"substance_details:{pesticide_residue_id}"
122
- cached_data = redis_client.get(cache_key)
123
- if cached_data:
124
- return eval(cached_data)
125
  url = f"{self.BASE_URL}/pesticide_residues/{pesticide_residue_id}?format=json&api-version=v2.0"
126
- response = await self.fetch_data(url)
127
  if not response or "value" not in response or not response["value"]:
128
  logger.warning(f"Pas de détails trouvés pour la substance {pesticide_residue_id}")
129
  return {"substance_name": f"Substance {pesticide_residue_id}"}
@@ -133,7 +128,7 @@ class PesticideDataFetcher:
133
  logger.warning(f"Nom de substance non trouvé pour l'ID {pesticide_residue_id}")
134
  return {"substance_name": f"Substance {pesticide_residue_id}"}
135
  active_url = f"{self.BASE_URL}/active_substances?format=json&substance_name={substance_name}&api-version=v2.0"
136
- active_response = await self.fetch_data(active_url)
137
  details = {
138
  "substance_name": substance_name,
139
  "status": None,
@@ -147,32 +142,25 @@ class PesticideDataFetcher:
147
  "approval_date": active_data.get("approval_date"),
148
  "expiry_date": active_data.get("expiry_date")
149
  })
150
- redis_client.set(cache_key, str(details), ex=3600)
151
  return details
152
 
153
- async def get_substance_name_by_id(self, substance_id: int) -> str:
154
  """Récupère le nom de la substance à partir de son ID."""
155
- cache_key = f"substance_name:{substance_id}"
156
- cached_data = redis_client.get(cache_key)
157
- if cached_data:
158
- return cached_data.decode('utf-8')
159
  url = f"{self.BASE_URL}/active_substances/{substance_id}?format=json&api-version=v2.0"
160
- response = await self.fetch_data(url)
161
  if response and "value" in response and response["value"]:
162
  substance_data = response["value"][0]
163
- substance_name = substance_data.get("substance_name", f"Substance {substance_id}")
164
- redis_client.set(cache_key, substance_name, ex=3600)
165
- return substance_name
166
  return f"Substance {substance_id}"
167
 
168
- async def get_all_substances(self) -> List[str]:
169
  """Récupère la liste complète des substances actives."""
170
  cache_key = "all_substances"
171
  cached_data = redis_client.get(cache_key)
172
  if cached_data:
173
  return eval(cached_data)
174
  url = f"{self.BASE_URL}/active_substances?format=json&api-version=v2.0"
175
- response = await self.fetch_data(url)
176
  substances = [item.get("substance_name") for item in response.get("value", [])]
177
  redis_client.set(cache_key, str(substances), ex=3600)
178
  return substances
@@ -181,11 +169,11 @@ class PesticideInterface:
181
  """Classe pour gérer l'interface utilisateur Gradio."""
182
  def __init__(self):
183
  self.fetcher = PesticideDataFetcher()
184
- self.products = asyncio.run(self.fetcher.get_products())
185
  self.product_choices = {
186
  p['product_name']: p['product_id'] for p in self.products
187
  }
188
- self.substances = asyncio.run(self.fetcher.get_all_substances())
189
  self._cache = {}
190
  logger.info(f"Initialized interface with {len(self.product_choices)} products and {len(self.substances)} substances.")
191
 
@@ -232,58 +220,71 @@ class PesticideInterface:
232
  return regulation_number
233
  return f'<a href="{regulation_url}" target="_blank">{regulation_number}</a>'
234
 
235
- async def get_product_details(self, product_name: str, period: str, show_only_changes: bool) -> pd.DataFrame:
236
  """Récupère et formate les détails des MRLs pour un produit donné."""
237
  logger.info(f"Récupération des détails pour le produit: {product_name}")
238
- if not product_name:
239
- return pd.DataFrame({"Message": ["Veuillez sélectionner un produit"]})
240
- product_id = self.product_choices.get(product_name)
241
- if not product_id:
242
- return pd.DataFrame({"Message": ["Produit non trouvé"]})
243
- cache_key = f"{product_id}_{period}_{show_only_changes}"
244
- if cache_key in self._cache:
245
- return self._cache[cache_key]
246
- mrls = await self.fetcher.get_mrls(product_id)
247
- if period != "Toutes les dates":
248
- mrls = self.filter_by_period(mrls, period)
249
- if not mrls:
250
- return pd.DataFrame({"Message": ["Aucune donnée trouvée pour la période sélectionnée"]})
251
- processed_mrls = []
252
- tasks = [self.fetcher.get_substance_details(mrl["pesticide_residue_id"]) for mrl in mrls]
253
- substance_details_list = await asyncio.gather(*tasks)
254
- for substance_details, mrl in zip(substance_details_list, mrls):
255
- logger.info(f"Détails de la substance récupérés: {substance_details}")
256
- mrl_value = mrl.get("mrl_value", "")
257
- if isinstance(mrl_value, (int, float)):
258
- formatted_mrl = f"{mrl_value}{'*' if str(mrl_value).endswith('*') else ''}"
259
- else:
260
- formatted_mrl = str(mrl_value)
261
- mrl_data = {
262
- "Substance": substance_details.get("substance_name", ""),
263
- "Valeur LMR": formatted_mrl,
264
- "Date d'application": self.parse_date(mrl.get("entry_into_force_date")),
265
- "Date de modification": self.parse_date(mrl.get("modification_date")),
266
- "Règlement": self.format_regulation_link(
267
- mrl.get("regulation_url", ""),
268
- mrl.get("regulation_number", "") or mrl.get("regulation_reference", "")
269
- ),
270
- "Statut": substance_details.get("status", ""),
271
- "Date d'approbation": self.parse_date(substance_details.get("approval_date")),
272
- "Date d'expiration": self.parse_date(substance_details.get("expiry_date"))
273
- }
274
- logger.info(f"Données MRL formatées: {mrl_data}")
275
- processed_mrls.append(mrl_data)
276
- df = pd.DataFrame(processed_mrls)
277
- if show_only_changes and "Date de modification" in df.columns:
278
- df = df[df["Date de modification"].notna()]
279
- df = df.sort_values("Date d'application", ascending=False)
280
- columns_order = [
281
- "Substance", "Valeur LMR", "Date d'application", "Date de modification",
282
- "Règlement", "Statut", "Date d'approbation", "Date d'expiration"
283
- ]
284
- df = df[columns_order]
285
- self._cache[cache_key] = df
286
- return df
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
  def create_graph(self, df: pd.DataFrame) -> gr.Plot:
289
  """Crée un graphique interactif pour les dates d'application."""
@@ -296,22 +297,26 @@ class PesticideInterface:
296
  df.to_csv(csv_file_path, index=False)
297
  return csv_file_path
298
 
299
- async def get_substance_details_table(self, substance_name: str) -> pd.DataFrame:
300
  """Récupère et formate les détails d'une substance active."""
301
  logger.info(f"Récupération des détails pour la substance: {substance_name}")
302
- if not substance_name:
303
- return pd.DataFrame({"Message": ["Veuillez sélectionner une substance"]})
304
- substance_details = await self.fetcher.get_active_substance_details(substance_name)
305
- if not substance_details:
306
- return pd.DataFrame({"Message": ["Substance non trouvée"]})
307
- df = pd.DataFrame([substance_details])
308
- df = df.rename(columns={
309
- "status": "Statut",
310
- "approval_date": "Date d'approbation",
311
- "expiry_date": "Date d'expiration"
312
- })
313
- df.insert(0, "Substance", substance_name)
314
- return df
 
 
 
 
315
 
316
  def create_interface(self) -> gr.Blocks:
317
  """Crée l'interface Gradio avec un design amélioré."""
@@ -362,7 +367,7 @@ class PesticideInterface:
362
  interactive=False
363
  )
364
  fetch_btn.click(
365
- fn=lambda product_name, period, show_only_changes: asyncio.run(self.get_product_details(product_name, period, show_only_changes)),
366
  inputs=[product_dropdown, period_radio, show_changes],
367
  outputs=[mrls_table]
368
  )
@@ -377,7 +382,7 @@ class PesticideInterface:
377
  outputs=export_output
378
  )
379
  substance_dropdown.change(
380
- fn=lambda substance_name: asyncio.run(self.get_substance_details_table(substance_name)),
381
  inputs=substance_dropdown,
382
  outputs=substance_table
383
  )
@@ -393,7 +398,7 @@ class PesticideInterface:
393
  """)
394
  return interface
395
 
396
- async def main():
397
  interface = PesticideInterface()
398
  app = interface.create_interface()
399
  app.launch(
@@ -403,5 +408,5 @@ async def main():
403
  )
404
 
405
  if __name__ == "__main__":
406
- asyncio.run(main())
407
 
 
4
  from typing import Dict, List, Optional
5
  import gradio as gr
6
  import pandas as pd
7
+ import requests
 
8
  from dataclasses import dataclass
9
  from tenacity import retry, stop_after_attempt, wait_fixed
10
  import plotly.express as px
 
44
 
45
  def __init__(self):
46
  self.session = self._create_session()
47
+ self._substance_cache = {}
48
+ self._product_cache = {}
49
 
50
  def _create_session(self):
51
  """Crée une session pour les requêtes HTTP."""
52
+ session = requests.Session()
53
+ for header, value in self.HEADERS.items():
54
+ session.headers[header] = value
55
+ return session
56
 
57
  @retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
58
+ def fetch_data(self, url: str) -> Dict:
59
  """Récupère les données depuis l'API avec gestion d'erreurs et retry."""
60
  try:
61
+ response = self.session.get(url, timeout=10)
62
+ response.raise_for_status()
63
+ data = response.json()
64
+ logger.info(f"Fetched data from {url}: {str(data)[:200]}...")
65
+ return data
66
+ except requests.RequestException as e:
67
  logger.error(f"Failed to fetch data from {url}: {str(e)}")
68
  return {"error": str(e)}
69
 
70
+ def get_active_substance_details(self, substance_name: str) -> Dict:
71
  """Récupère les détails d'une substance active."""
72
  cache_key = f"substance:{substance_name}"
73
  cached_data = redis_client.get(cache_key)
74
  if cached_data:
75
  return eval(cached_data)
76
  url = f"{self.BASE_URL}/active_substances?format=json&substance_name={substance_name}&api-version=v2.0"
77
+ response = self.fetch_data(url)
78
  if response and "value" in response and response["value"]:
79
  substance_data = response["value"][0]
80
  substance_details = {
 
86
  return substance_details
87
  return {}
88
 
89
+ def get_products(self) -> List[Dict]:
90
  """Récupère la liste complète des produits avec pagination."""
91
  cache_key = "products"
92
  cached_data = redis_client.get(cache_key)
 
96
  base_url = f"{self.BASE_URL}/pesticide_residues_products?format=json&language=FR&api-version=v2.0"
97
  url = base_url
98
  while url:
99
+ response = self.fetch_data(url)
100
  if not response or "value" not in response:
101
  break
102
  all_products.extend(response["value"])
 
109
  logger.info(f"Récupéré {len(all_products)} produits au total")
110
  return all_products
111
 
112
+ def get_mrls(self, product_id: int) -> List[Dict]:
113
  """Récupère les LMR pour un produit spécifique."""
 
 
 
 
114
  url = f"{self.BASE_URL}/pesticide_residues_mrls?format=json&product_id={product_id}&api-version=v2.0"
115
+ response = self.fetch_data(url)
 
116
  return response.get("value", [])
117
 
118
+ def get_substance_details(self, pesticide_residue_id: int) -> Dict:
119
  """Récupère les détails d'une substance à partir de son ID."""
 
 
 
 
120
  url = f"{self.BASE_URL}/pesticide_residues/{pesticide_residue_id}?format=json&api-version=v2.0"
121
+ response = self.fetch_data(url)
122
  if not response or "value" not in response or not response["value"]:
123
  logger.warning(f"Pas de détails trouvés pour la substance {pesticide_residue_id}")
124
  return {"substance_name": f"Substance {pesticide_residue_id}"}
 
128
  logger.warning(f"Nom de substance non trouvé pour l'ID {pesticide_residue_id}")
129
  return {"substance_name": f"Substance {pesticide_residue_id}"}
130
  active_url = f"{self.BASE_URL}/active_substances?format=json&substance_name={substance_name}&api-version=v2.0"
131
+ active_response = self.fetch_data(active_url)
132
  details = {
133
  "substance_name": substance_name,
134
  "status": None,
 
142
  "approval_date": active_data.get("approval_date"),
143
  "expiry_date": active_data.get("expiry_date")
144
  })
 
145
  return details
146
 
147
+ def get_substance_name_by_id(self, substance_id: int) -> str:
148
  """Récupère le nom de la substance à partir de son ID."""
 
 
 
 
149
  url = f"{self.BASE_URL}/active_substances/{substance_id}?format=json&api-version=v2.0"
150
+ response = self.fetch_data(url)
151
  if response and "value" in response and response["value"]:
152
  substance_data = response["value"][0]
153
+ return substance_data.get("substance_name", f"Substance {substance_id}")
 
 
154
  return f"Substance {substance_id}"
155
 
156
+ def get_all_substances(self) -> List[str]:
157
  """Récupère la liste complète des substances actives."""
158
  cache_key = "all_substances"
159
  cached_data = redis_client.get(cache_key)
160
  if cached_data:
161
  return eval(cached_data)
162
  url = f"{self.BASE_URL}/active_substances?format=json&api-version=v2.0"
163
+ response = self.fetch_data(url)
164
  substances = [item.get("substance_name") for item in response.get("value", [])]
165
  redis_client.set(cache_key, str(substances), ex=3600)
166
  return substances
 
169
  """Classe pour gérer l'interface utilisateur Gradio."""
170
  def __init__(self):
171
  self.fetcher = PesticideDataFetcher()
172
+ self.products = self.fetcher.get_products()
173
  self.product_choices = {
174
  p['product_name']: p['product_id'] for p in self.products
175
  }
176
+ self.substances = self.fetcher.get_all_substances()
177
  self._cache = {}
178
  logger.info(f"Initialized interface with {len(self.product_choices)} products and {len(self.substances)} substances.")
179
 
 
220
  return regulation_number
221
  return f'<a href="{regulation_url}" target="_blank">{regulation_number}</a>'
222
 
223
+ def get_product_details(self, product_name: str, period: str, show_only_changes: bool) -> pd.DataFrame:
224
  """Récupère et formate les détails des MRLs pour un produit donné."""
225
  logger.info(f"Récupération des détails pour le produit: {product_name}")
226
+ try:
227
+ if not product_name:
228
+ return pd.DataFrame({"Message": ["Veuillez sélectionner un produit"]})
229
+ product_id = self.product_choices.get(product_name)
230
+ if not product_id:
231
+ return pd.DataFrame({"Message": ["Produit non trouvé"]})
232
+ cache_key = f"{product_id}_{period}_{show_only_changes}"
233
+ if cache_key in self._cache:
234
+ return self._cache[cache_key]
235
+ mrls = self.fetcher.get_mrls(product_id)
236
+ if period != "Toutes les dates":
237
+ mrls = self.filter_by_period(mrls, period)
238
+ if not mrls:
239
+ return pd.DataFrame({"Message": ["Aucune donnée trouvée pour la période sélectionnée"]})
240
+ processed_mrls = []
241
+ with ThreadPoolExecutor(max_workers=10) as executor:
242
+ futures = {
243
+ executor.submit(self.fetcher.get_substance_details, mrl["pesticide_residue_id"]): mrl
244
+ for mrl in mrls
245
+ }
246
+ for future in futures:
247
+ mrl = futures[future]
248
+ try:
249
+ substance_details = future.result()
250
+ logger.info(f"Détails de la substance récupérés: {substance_details}")
251
+ # Formatage de la valeur LMR
252
+ mrl_value = mrl.get("mrl_value", "")
253
+ if isinstance(mrl_value, (int, float)):
254
+ formatted_mrl = f"{mrl_value}{'*' if str(mrl_value).endswith('*') else ''}"
255
+ else:
256
+ formatted_mrl = str(mrl_value)
257
+ mrl_data = {
258
+ "Substance": substance_details.get("substance_name", ""),
259
+ "Valeur LMR": formatted_mrl,
260
+ "Date d'application": self.parse_date(mrl.get("entry_into_force_date")),
261
+ "Date de modification": self.parse_date(mrl.get("modification_date")),
262
+ "Règlement": self.format_regulation_link(
263
+ mrl.get("regulation_url", ""),
264
+ mrl.get("regulation_number", "") or mrl.get("regulation_reference", "")
265
+ ),
266
+ "Statut": substance_details.get("status", ""),
267
+ "Date d'approbation": self.parse_date(substance_details.get("approval_date")),
268
+ "Date d'expiration": self.parse_date(substance_details.get("expiry_date"))
269
+ }
270
+ logger.info(f"Données MRL formatées: {mrl_data}")
271
+ processed_mrls.append(mrl_data)
272
+ except Exception as e:
273
+ logger.error(f"Erreur lors du traitement de la substance: {e}")
274
+ df = pd.DataFrame(processed_mrls)
275
+ if show_only_changes and "Date de modification" in df.columns:
276
+ df = df[df["Date de modification"].notna()]
277
+ df = df.sort_values("Date d'application", ascending=False)
278
+ columns_order = [
279
+ "Substance", "Valeur LMR", "Date d'application", "Date de modification",
280
+ "Règlement", "Statut", "Date d'approbation", "Date d'expiration"
281
+ ]
282
+ df = df[columns_order]
283
+ self._cache[cache_key] = df
284
+ return df
285
+ except Exception as e:
286
+ logger.error(f"Erreur dans get_product_details: {str(e)}")
287
+ return pd.DataFrame({"Message": [f"Erreur: {str(e)}"]})
288
 
289
  def create_graph(self, df: pd.DataFrame) -> gr.Plot:
290
  """Crée un graphique interactif pour les dates d'application."""
 
297
  df.to_csv(csv_file_path, index=False)
298
  return csv_file_path
299
 
300
+ def get_substance_details_table(self, substance_name: str) -> pd.DataFrame:
301
  """Récupère et formate les détails d'une substance active."""
302
  logger.info(f"Récupération des détails pour la substance: {substance_name}")
303
+ try:
304
+ if not substance_name:
305
+ return pd.DataFrame({"Message": ["Veuillez sélectionner une substance"]})
306
+ substance_details = self.fetcher.get_active_substance_details(substance_name)
307
+ if not substance_details:
308
+ return pd.DataFrame({"Message": ["Substance non trouvée"]})
309
+ df = pd.DataFrame([substance_details])
310
+ df = df.rename(columns={
311
+ "status": "Statut",
312
+ "approval_date": "Date d'approbation",
313
+ "expiry_date": "Date d'expiration"
314
+ })
315
+ df.insert(0, "Substance", substance_name)
316
+ return df
317
+ except Exception as e:
318
+ logger.error(f"Erreur dans get_substance_details_table: {str(e)}")
319
+ return pd.DataFrame({"Message": [f"Erreur: {str(e)}"]})
320
 
321
  def create_interface(self) -> gr.Blocks:
322
  """Crée l'interface Gradio avec un design amélioré."""
 
367
  interactive=False
368
  )
369
  fetch_btn.click(
370
+ fn=self.get_product_details,
371
  inputs=[product_dropdown, period_radio, show_changes],
372
  outputs=[mrls_table]
373
  )
 
382
  outputs=export_output
383
  )
384
  substance_dropdown.change(
385
+ fn=self.get_substance_details_table,
386
  inputs=substance_dropdown,
387
  outputs=substance_table
388
  )
 
398
  """)
399
  return interface
400
 
401
+ def main():
402
  interface = PesticideInterface()
403
  app = interface.create_interface()
404
  app.launch(
 
408
  )
409
 
410
  if __name__ == "__main__":
411
+ main()
412