MMOON commited on
Commit
b8b6b12
·
verified ·
1 Parent(s): 6f85c97

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +87 -86
app.py CHANGED
@@ -53,15 +53,15 @@ class PesticideDataFetcher:
53
  self._product_cache: Dict[int, Dict[str, Any]] = {}
54
  self._mrl_cache: Dict[int, List[Dict[str, Any]]] = {}
55
  self.use_cache = use_cache
56
-
57
  # Création du répertoire de cache si nécessaire
58
  if not os.path.exists(self.CACHE_DIR):
59
  os.makedirs(self.CACHE_DIR)
60
-
61
  # Chargement des caches si disponibles
62
  if use_cache:
63
  self._load_caches()
64
-
65
  # Préchargement des substances si le cache est vide
66
  if not self._substance_cache:
67
  self.preload_substance_names()
@@ -75,12 +75,12 @@ class PesticideDataFetcher:
75
  for substance_id, substance_info in substance_data.items():
76
  self._substance_cache[int(substance_id)] = SubstanceDetails(**substance_info)
77
  logger.info(f"Cache de substances chargé: {len(self._substance_cache)} substances")
78
-
79
  if os.path.exists(self.PRODUCT_CACHE_FILE):
80
  with open(self.PRODUCT_CACHE_FILE, 'r', encoding='utf-8') as f:
81
  self._product_cache = {int(k): v for k, v in json.load(f).items()}
82
  logger.info(f"Cache de produits chargé: {len(self._product_cache)} produits")
83
-
84
  if os.path.exists(self.MRL_CACHE_FILE):
85
  with open(self.MRL_CACHE_FILE, 'r', encoding='utf-8') as f:
86
  self._mrl_cache = {int(k): v for k, v in json.load(f).items()}
@@ -108,16 +108,16 @@ class PesticideDataFetcher:
108
  }
109
  for substance_id, details in self._substance_cache.items()
110
  }
111
-
112
  with open(self.SUBSTANCE_CACHE_FILE, 'w', encoding='utf-8') as f:
113
  json.dump(substance_data, f, ensure_ascii=False, indent=2)
114
-
115
  with open(self.PRODUCT_CACHE_FILE, 'w', encoding='utf-8') as f:
116
  json.dump({str(k): v for k, v in self._product_cache.items()}, f, ensure_ascii=False, indent=2)
117
-
118
  with open(self.MRL_CACHE_FILE, 'w', encoding='utf-8') as f:
119
  json.dump({str(k): v for k, v in self._mrl_cache.items()}, f, ensure_ascii=False, indent=2)
120
-
121
  logger.info("Tous les caches ont été sauvegardés avec succès")
122
  except Exception as e:
123
  logger.error(f"Erreur lors de la sauvegarde des caches: {e}")
@@ -158,7 +158,7 @@ class PesticideDataFetcher:
158
  ec_number=substance.get("ecNumber")
159
  )
160
  substances_loaded += 1
161
-
162
  # Chargement des détails supplémentaires pour chaque substance
163
  self._load_substance_details(substance_id)
164
 
@@ -167,7 +167,7 @@ class PesticideDataFetcher:
167
 
168
  if self.use_cache:
169
  self._save_caches()
170
-
171
  logger.info(f"Préchargement terminé. Total des substances: {len(self._substance_cache)}")
172
 
173
  def _load_substance_details(self, substance_id: int) -> None:
@@ -175,7 +175,7 @@ class PesticideDataFetcher:
175
  url = f"{self.BASE_URL}/pesticide_residues/{substance_id}?format=json&language=FR&api-version=v2.0"
176
  try:
177
  data = self.fetch_data(url)
178
-
179
  substance = self._substance_cache.get(substance_id)
180
  if substance:
181
  substance.approval_date = data.get("approvalDate")
@@ -190,14 +190,14 @@ class PesticideDataFetcher:
190
  """Récupère le nom d'une substance à partir de son ID"""
191
  if substance_id in self._substance_cache:
192
  return self._substance_cache[substance_id].name
193
-
194
  # Si la substance n'est pas dans le cache, essayer de la récupérer
195
  try:
196
  url = f"{self.BASE_URL}/pesticide_residues/{substance_id}?format=json&language=FR&api-version=v2.0"
197
  data = self.fetch_data(url)
198
-
199
  substance_name = data.get("pesticideResidueName", f"Substance {substance_id}")
200
-
201
  # Mettre à jour le cache
202
  self._substance_cache[substance_id] = SubstanceDetails(
203
  name=substance_name,
@@ -208,42 +208,42 @@ class PesticideDataFetcher:
208
  cas_number=data.get("casNumber"),
209
  ec_number=data.get("ecNumber")
210
  )
211
-
212
  if self.use_cache:
213
  self._save_caches()
214
-
215
  return substance_name
216
  except Exception as e:
217
  logger.error(f"Erreur lors de la récupération de la substance {substance_id}: {e}")
218
  return f"Substance inconnue ({substance_id})"
219
-
220
  def get_product_list(self) -> List[Dict[str, Any]]:
221
  """Récupère la liste de tous les produits"""
222
  if self._product_cache:
223
  return list(self._product_cache.values())
224
-
225
  logger.info("Récupération de la liste des produits...")
226
  url = f"{self.BASE_URL}/pesticide_residues_products?format=json&language=FR&api-version=v2.0"
227
  products_loaded = 0
228
-
229
  while url:
230
  data = self.fetch_data(url)
231
  if "error" in data:
232
  logger.error(f"Erreur produits: {data.get('error', 'Aucune info')}")
233
  break
234
-
235
  for product in data.get("value", []):
236
  product_id = product.get("productId")
237
  if product_id:
238
  self._product_cache[product_id] = product
239
  products_loaded += 1
240
-
241
  url = data.get("nextLink")
242
  logger.info(f"Produits récupérés jusqu'à présent: {products_loaded}")
243
-
244
  if self.use_cache:
245
  self._save_caches()
246
-
247
  logger.info(f"Total des produits récupérés: {len(self._product_cache)}")
248
  return list(self._product_cache.values())
249
 
@@ -252,26 +252,26 @@ class PesticideDataFetcher:
252
  # Vérifier d'abord dans le cache
253
  if product_id in self._mrl_cache:
254
  return self._mrl_cache[product_id]
255
-
256
  logger.info(f"Récupération des LMR pour le produit {product_id}...")
257
  url = f"{self.BASE_URL}/pesticide_residues_products/{product_id}/mrls?format=json&language=FR&api-version=v2.0"
258
-
259
  mrls = []
260
  while url:
261
  data = self.fetch_data(url)
262
  if "error" in data:
263
  logger.error(f"Erreur lors de la récupération des LMR: {data.get('error', 'Aucune info')}")
264
  break
265
-
266
  mrls.extend(data.get("value", []))
267
  url = data.get("nextLink")
268
-
269
  # Mise à jour du cache
270
  self._mrl_cache[product_id] = mrls
271
-
272
  if self.use_cache:
273
  self._save_caches()
274
-
275
  logger.info(f"LMR récupérées pour le produit {product_id}: {len(mrls)}")
276
  return mrls
277
 
@@ -283,22 +283,22 @@ class PesticideDataFetcher:
283
  if query in substance.name.lower()
284
  ]
285
  return sorted(results, key=lambda x: x.name)
286
-
287
- def get_substance_mrls(self, substance_id: int) -> Dict[str, Any]:
288
  """Récupère tous les produits avec LMR pour une substance donnée"""
289
  logger.info(f"Récupération des LMR pour la substance {substance_id}...")
290
  url = f"{self.BASE_URL}/pesticide_residues/{substance_id}/mrls?format=json&language=FR&api-version=v2.0"
291
-
292
  all_mrls = []
293
  while url:
294
  data = self.fetch_data(url)
295
  if "error" in data:
296
  logger.error(f"Erreur lors de la récupération des LMR: {data.get('error', 'Aucune info')}")
297
  break
298
-
299
  all_mrls.extend(data.get("value", []))
300
  url = data.get("nextLink")
301
-
302
  logger.info(f"LMR récupérées pour la substance {substance_id}: {len(all_mrls)}")
303
  return all_mrls
304
 
@@ -316,7 +316,7 @@ class PesticideApp:
316
  def __init__(self, use_cache: bool = True):
317
  logger.info("Initialisation de l'application...")
318
  self.fetcher = PesticideDataFetcher(use_cache=use_cache)
319
-
320
  # Récupération de la liste des produits
321
  logger.info("Récupération de la liste des produits...")
322
  products = self.fetcher.get_product_list()
@@ -324,12 +324,12 @@ class PesticideApp:
324
  p.get('productName', 'Sans nom'): p.get('productId', 0)
325
  for p in products
326
  }
327
-
328
  # Préparation de la liste des substances
329
  self.substances = sorted([
330
  sd.name for sd in self.fetcher._substance_cache.values()
331
  ])
332
-
333
  logger.info(f"Application initialisée avec {len(self.product_list)} produits et {len(self.substances)} substances")
334
 
335
  def format_date(self, date_str: Optional[str]) -> str:
@@ -342,17 +342,17 @@ class PesticideApp:
342
  product_id = self.product_list.get(product_name)
343
  if not product_id:
344
  return pd.DataFrame([{"Erreur": "Produit non trouvé"}])
345
-
346
  mrls = self.fetcher.get_mrls(product_id)
347
  if not mrls:
348
  return pd.DataFrame([{"Erreur": "Aucune donnée LMR trouvée"}])
349
-
350
  data = []
351
  for mrl in mrls:
352
  substance_id = mrl.get("pesticideResidueId", 0)
353
  substance_name = self.fetcher.get_substance_name(substance_id)
354
  substance = self.fetcher._substance_cache.get(substance_id)
355
-
356
  data.append({
357
  "Substance": substance_name,
358
  "Valeur LMR": mrl.get("mrlValue", "N/C"),
@@ -364,7 +364,7 @@ class PesticideApp:
364
  "Date d'approbation": self.format_date(getattr(substance, "approval_date", None)),
365
  "Date d'expiration": self.format_date(getattr(substance, "expiry_date", None))
366
  })
367
-
368
  df = pd.DataFrame(data)
369
  logger.info(f"Détails récupérés pour {product_name}: {len(df)} entrées")
370
  return df
@@ -374,11 +374,11 @@ class PesticideApp:
374
  logger.info(f"Recherche de substances: {substance_query}")
375
  if not substance_query or len(substance_query) < 3:
376
  return pd.DataFrame([{"Message": "Veuillez entrer au moins 3 caractères pour la recherche"}])
377
-
378
  results = self.fetcher.search_substances(substance_query)
379
  if not results:
380
  return pd.DataFrame([{"Message": "Aucune substance trouvée"}])
381
-
382
  data = []
383
  for substance in results:
384
  data.append({
@@ -390,34 +390,35 @@ class PesticideApp:
390
  "Date d'approbation": self.format_date(substance.approval_date),
391
  "Date d'expiration": self.format_date(substance.expiry_date)
392
  })
393
-
394
  df = pd.DataFrame(data)
395
  logger.info(f"Résultats de la recherche pour '{substance_query}': {len(df)} substances trouvées")
396
  return df
397
 
398
  def get_substance_mrls(self, substance_id: int) -> pd.DataFrame:
399
- """Récupère tous les produits avec LMR pour une substance donnée"""
400
  logger.info(f"Récupération des LMR pour la substance ID: {substance_id}")
401
  if not substance_id:
402
  return pd.DataFrame([{"Erreur": "ID de substance non valide"}])
403
-
404
  substance = self.fetcher._substance_cache.get(substance_id)
405
  if not substance:
406
  return pd.DataFrame([{"Erreur": "Substance non trouvée"}])
407
-
408
- mrls = self.fetcher.get_substance_mrls(substance_id)
409
- if not mrls:
410
  return pd.DataFrame([{"Message": f"Aucune LMR trouvée pour {substance.name}"}])
411
-
 
412
  data = []
413
- for mrl in mrls:
414
  product_id = mrl.get("productId")
415
  product_name = "Inconnu"
416
-
417
  # Récupérer le nom du produit si possible
418
  if product_id in self.fetcher._product_cache:
419
  product_name = self.fetcher._product_cache[product_id].get("productName", "Inconnu")
420
-
421
  data.append({
422
  "Produit": product_name,
423
  "Valeur LMR": mrl.get("mrlValue", "N/C"),
@@ -425,10 +426,10 @@ class PesticideApp:
425
  "Date d'effet": self.format_date(mrl.get("entryIntoForceDate")),
426
  "Notes": mrl.get("footnotes", "")
427
  })
428
-
429
- df = pd.DataFrame(data)
430
  logger.info(f"LMR récupérées pour {substance.name}: {len(df)} entrées")
431
- return df
432
 
433
  def create_histogram(self, df: pd.DataFrame) -> go.Figure:
434
  """Crée un histogramme des valeurs LMR"""
@@ -436,7 +437,7 @@ class PesticideApp:
436
  fig = go.Figure()
437
  fig.update_layout(title="Aucune donnée disponible pour l'histogramme")
438
  return fig
439
-
440
  # Convertir les valeurs LMR en nombres si possible
441
  numeric_values = []
442
  for val in df["Valeur LMR"]:
@@ -450,12 +451,12 @@ class PesticideApp:
450
  numeric_values.append(float(val))
451
  except (ValueError, TypeError):
452
  continue
453
-
454
  if not numeric_values:
455
  fig = go.Figure()
456
  fig.update_layout(title="Aucune valeur numérique disponible pour l'histogramme")
457
  return fig
458
-
459
  # Création de l'histogramme
460
  fig = go.Figure(data=[go.Histogram(x=numeric_values, nbinsx=20)])
461
  fig.update_layout(
@@ -472,16 +473,16 @@ class PesticideApp:
472
  fig = go.Figure()
473
  fig.update_layout(title=f"Aucune donnée disponible pour {column}")
474
  return fig
475
-
476
  # Compter les valeurs
477
  value_counts = df[column].value_counts()
478
-
479
  # Création du graphique
480
  fig = go.Figure(data=[go.Pie(
481
  labels=value_counts.index,
482
  values=value_counts.values
483
  )])
484
-
485
  fig.update_layout(title=f"Répartition par {column}")
486
  return fig
487
 
@@ -494,7 +495,7 @@ class PesticideApp:
494
  <p>Consultez les limites maximales de résidus (LMR) de pesticides dans les produits alimentaires</p>
495
  </div>
496
  """)
497
-
498
  with gr.Tab("Recherche par Produit"):
499
  with gr.Row():
500
  with gr.Column(scale=3):
@@ -505,20 +506,20 @@ class PesticideApp:
505
  )
506
  with gr.Column(scale=1):
507
  search_btn = gr.Button("Rechercher", variant="primary")
508
-
509
  with gr.Row():
510
  output = gr.Dataframe(
511
  headers=["Substance", "Valeur LMR", "Unité", "Date d'effet", "Statut"],
512
  max_rows=20,
513
  interactive=False
514
  )
515
-
516
  with gr.Row():
517
  with gr.Column():
518
  histogram = gr.Plot(label="Distribution des LMR")
519
  with gr.Column():
520
  status_pie = gr.Plot(label="Statut des substances")
521
-
522
  search_btn.click(
523
  fn=lambda p: (
524
  self.get_product_details(p),
@@ -528,7 +529,7 @@ class PesticideApp:
528
  inputs=[product],
529
  outputs=[output, histogram, status_pie]
530
  )
531
-
532
  with gr.Tab("Recherche par Substance"):
533
  with gr.Row():
534
  with gr.Column(scale=3):
@@ -539,69 +540,69 @@ class PesticideApp:
539
  )
540
  with gr.Column(scale=1):
541
  substance_search_btn = gr.Button("Rechercher", variant="primary")
542
-
543
  substance_results = gr.Dataframe(
544
  headers=["ID", "Nom", "Statut", "N° CAS", "N° EC", "Date d'approbation", "Date d'expiration"],
545
  max_rows=15,
546
  interactive=True
547
  )
548
-
549
  substance_select = gr.Number(label="ID de la substance sélectionnée", interactive=True, visible=False)
550
  substance_mrls_btn = gr.Button("Voir les LMR pour cette substance", visible=True)
551
-
552
  substance_mrls = gr.Dataframe(
553
  headers=["Produit", "Valeur LMR", "Unité", "Date d'effet", "Notes"],
554
  max_rows=20,
555
  interactive=False
556
  )
557
-
558
  mrl_histogram = gr.Plot(label="Distribution des LMR par produit")
559
-
560
  # Événements
561
  substance_search_btn.click(
562
  fn=self.search_substance,
563
  inputs=[substance_query],
564
  outputs=[substance_results]
565
  )
566
-
567
  # Mise à jour de l'ID de substance sélectionné
568
  substance_results.select(
569
  fn=lambda evt: evt["data"]["ID"] if evt and "data" in evt and "ID" in evt["data"] else None,
570
  inputs=[],
571
  outputs=[substance_select]
572
  )
573
-
574
  substance_mrls_btn.click(
575
  fn=lambda sid: (
576
- self.get_substance_mrls(sid),
577
- self.create_histogram(self.get_substance_mrls(sid))
578
  ),
579
  inputs=[substance_select],
580
  outputs=[substance_mrls, mrl_histogram]
581
  )
582
-
583
  with gr.Tab("À propos"):
584
  gr.HTML("""
585
  <div style="padding: 20px;">
586
  <h2>À propos de cette application</h2>
587
- <p>Cette application permet de consulter les limites maximales de résidus (LMR) des pesticides
588
  autorisés dans les produits alimentaires dans l'Union européenne.</p>
589
-
590
  <h3>Sources de données</h3>
591
  <p>Les données sont extraites de l'API officielle de la Commission européenne:</p>
592
  <ul>
593
  <li>API Pesticides: <a href="https://api.datalake.sante.service.ec.europa.eu/sante/pesticides">
594
  https://api.datalake.sante.service.ec.europa.eu/sante/pesticides</a></li>
595
  </ul>
596
-
597
  <h3>Comment utiliser cette application</h3>
598
  <ul>
599
- <li><strong>Recherche par Produit</strong>: Sélectionnez un produit alimentaire pour voir
600
  toutes les substances et leurs LMR associées.</li>
601
- <li><strong>Recherche par Substance</strong>: Recherchez une substance active pour voir son
602
  statut et les produits dans lesquels elle est réglementée.</li>
603
  </ul>
604
-
605
  <h3>Légende</h3>
606
  <ul>
607
  <li><strong>LMR</strong>: Limite Maximale de Résidus, exprimée généralement en mg/kg</li>
@@ -610,13 +611,13 @@ class PesticideApp:
610
  </ul>
611
  </div>
612
  """)
613
-
614
  return ui
615
 
616
  def main():
617
  # Paramètres de ligne de commande (vous pourriez les ajouter via argparse)
618
  use_cache = True # Utiliser le cache par défaut
619
-
620
  try:
621
  app = PesticideApp(use_cache=use_cache)
622
  logger.info("Lancement de l'interface utilisateur...")
 
53
  self._product_cache: Dict[int, Dict[str, Any]] = {}
54
  self._mrl_cache: Dict[int, List[Dict[str, Any]]] = {}
55
  self.use_cache = use_cache
56
+
57
  # Création du répertoire de cache si nécessaire
58
  if not os.path.exists(self.CACHE_DIR):
59
  os.makedirs(self.CACHE_DIR)
60
+
61
  # Chargement des caches si disponibles
62
  if use_cache:
63
  self._load_caches()
64
+
65
  # Préchargement des substances si le cache est vide
66
  if not self._substance_cache:
67
  self.preload_substance_names()
 
75
  for substance_id, substance_info in substance_data.items():
76
  self._substance_cache[int(substance_id)] = SubstanceDetails(**substance_info)
77
  logger.info(f"Cache de substances chargé: {len(self._substance_cache)} substances")
78
+
79
  if os.path.exists(self.PRODUCT_CACHE_FILE):
80
  with open(self.PRODUCT_CACHE_FILE, 'r', encoding='utf-8') as f:
81
  self._product_cache = {int(k): v for k, v in json.load(f).items()}
82
  logger.info(f"Cache de produits chargé: {len(self._product_cache)} produits")
83
+
84
  if os.path.exists(self.MRL_CACHE_FILE):
85
  with open(self.MRL_CACHE_FILE, 'r', encoding='utf-8') as f:
86
  self._mrl_cache = {int(k): v for k, v in json.load(f).items()}
 
108
  }
109
  for substance_id, details in self._substance_cache.items()
110
  }
111
+
112
  with open(self.SUBSTANCE_CACHE_FILE, 'w', encoding='utf-8') as f:
113
  json.dump(substance_data, f, ensure_ascii=False, indent=2)
114
+
115
  with open(self.PRODUCT_CACHE_FILE, 'w', encoding='utf-8') as f:
116
  json.dump({str(k): v for k, v in self._product_cache.items()}, f, ensure_ascii=False, indent=2)
117
+
118
  with open(self.MRL_CACHE_FILE, 'w', encoding='utf-8') as f:
119
  json.dump({str(k): v for k, v in self._mrl_cache.items()}, f, ensure_ascii=False, indent=2)
120
+
121
  logger.info("Tous les caches ont été sauvegardés avec succès")
122
  except Exception as e:
123
  logger.error(f"Erreur lors de la sauvegarde des caches: {e}")
 
158
  ec_number=substance.get("ecNumber")
159
  )
160
  substances_loaded += 1
161
+
162
  # Chargement des détails supplémentaires pour chaque substance
163
  self._load_substance_details(substance_id)
164
 
 
167
 
168
  if self.use_cache:
169
  self._save_caches()
170
+
171
  logger.info(f"Préchargement terminé. Total des substances: {len(self._substance_cache)}")
172
 
173
  def _load_substance_details(self, substance_id: int) -> None:
 
175
  url = f"{self.BASE_URL}/pesticide_residues/{substance_id}?format=json&language=FR&api-version=v2.0"
176
  try:
177
  data = self.fetch_data(url)
178
+
179
  substance = self._substance_cache.get(substance_id)
180
  if substance:
181
  substance.approval_date = data.get("approvalDate")
 
190
  """Récupère le nom d'une substance à partir de son ID"""
191
  if substance_id in self._substance_cache:
192
  return self._substance_cache[substance_id].name
193
+
194
  # Si la substance n'est pas dans le cache, essayer de la récupérer
195
  try:
196
  url = f"{self.BASE_URL}/pesticide_residues/{substance_id}?format=json&language=FR&api-version=v2.0"
197
  data = self.fetch_data(url)
198
+
199
  substance_name = data.get("pesticideResidueName", f"Substance {substance_id}")
200
+
201
  # Mettre à jour le cache
202
  self._substance_cache[substance_id] = SubstanceDetails(
203
  name=substance_name,
 
208
  cas_number=data.get("casNumber"),
209
  ec_number=data.get("ecNumber")
210
  )
211
+
212
  if self.use_cache:
213
  self._save_caches()
214
+
215
  return substance_name
216
  except Exception as e:
217
  logger.error(f"Erreur lors de la récupération de la substance {substance_id}: {e}")
218
  return f"Substance inconnue ({substance_id})"
219
+
220
  def get_product_list(self) -> List[Dict[str, Any]]:
221
  """Récupère la liste de tous les produits"""
222
  if self._product_cache:
223
  return list(self._product_cache.values())
224
+
225
  logger.info("Récupération de la liste des produits...")
226
  url = f"{self.BASE_URL}/pesticide_residues_products?format=json&language=FR&api-version=v2.0"
227
  products_loaded = 0
228
+
229
  while url:
230
  data = self.fetch_data(url)
231
  if "error" in data:
232
  logger.error(f"Erreur produits: {data.get('error', 'Aucune info')}")
233
  break
234
+
235
  for product in data.get("value", []):
236
  product_id = product.get("productId")
237
  if product_id:
238
  self._product_cache[product_id] = product
239
  products_loaded += 1
240
+
241
  url = data.get("nextLink")
242
  logger.info(f"Produits récupérés jusqu'à présent: {products_loaded}")
243
+
244
  if self.use_cache:
245
  self._save_caches()
246
+
247
  logger.info(f"Total des produits récupérés: {len(self._product_cache)}")
248
  return list(self._product_cache.values())
249
 
 
252
  # Vérifier d'abord dans le cache
253
  if product_id in self._mrl_cache:
254
  return self._mrl_cache[product_id]
255
+
256
  logger.info(f"Récupération des LMR pour le produit {product_id}...")
257
  url = f"{self.BASE_URL}/pesticide_residues_products/{product_id}/mrls?format=json&language=FR&api-version=v2.0"
258
+
259
  mrls = []
260
  while url:
261
  data = self.fetch_data(url)
262
  if "error" in data:
263
  logger.error(f"Erreur lors de la récupération des LMR: {data.get('error', 'Aucune info')}")
264
  break
265
+
266
  mrls.extend(data.get("value", []))
267
  url = data.get("nextLink")
268
+
269
  # Mise à jour du cache
270
  self._mrl_cache[product_id] = mrls
271
+
272
  if self.use_cache:
273
  self._save_caches()
274
+
275
  logger.info(f"LMR récupérées pour le produit {product_id}: {len(mrls)}")
276
  return mrls
277
 
 
283
  if query in substance.name.lower()
284
  ]
285
  return sorted(results, key=lambda x: x.name)
286
+
287
+ def get_substance_mrls(self, substance_id: int) -> List[Dict[str,Any]]: #Changed List to Dict
288
  """Récupère tous les produits avec LMR pour une substance donnée"""
289
  logger.info(f"Récupération des LMR pour la substance {substance_id}...")
290
  url = f"{self.BASE_URL}/pesticide_residues/{substance_id}/mrls?format=json&language=FR&api-version=v2.0"
291
+
292
  all_mrls = []
293
  while url:
294
  data = self.fetch_data(url)
295
  if "error" in data:
296
  logger.error(f"Erreur lors de la récupération des LMR: {data.get('error', 'Aucune info')}")
297
  break
298
+
299
  all_mrls.extend(data.get("value", []))
300
  url = data.get("nextLink")
301
+
302
  logger.info(f"LMR récupérées pour la substance {substance_id}: {len(all_mrls)}")
303
  return all_mrls
304
 
 
316
  def __init__(self, use_cache: bool = True):
317
  logger.info("Initialisation de l'application...")
318
  self.fetcher = PesticideDataFetcher(use_cache=use_cache)
319
+
320
  # Récupération de la liste des produits
321
  logger.info("Récupération de la liste des produits...")
322
  products = self.fetcher.get_product_list()
 
324
  p.get('productName', 'Sans nom'): p.get('productId', 0)
325
  for p in products
326
  }
327
+
328
  # Préparation de la liste des substances
329
  self.substances = sorted([
330
  sd.name for sd in self.fetcher._substance_cache.values()
331
  ])
332
+
333
  logger.info(f"Application initialisée avec {len(self.product_list)} produits et {len(self.substances)} substances")
334
 
335
  def format_date(self, date_str: Optional[str]) -> str:
 
342
  product_id = self.product_list.get(product_name)
343
  if not product_id:
344
  return pd.DataFrame([{"Erreur": "Produit non trouvé"}])
345
+
346
  mrls = self.fetcher.get_mrls(product_id)
347
  if not mrls:
348
  return pd.DataFrame([{"Erreur": "Aucune donnée LMR trouvée"}])
349
+
350
  data = []
351
  for mrl in mrls:
352
  substance_id = mrl.get("pesticideResidueId", 0)
353
  substance_name = self.fetcher.get_substance_name(substance_id)
354
  substance = self.fetcher._substance_cache.get(substance_id)
355
+
356
  data.append({
357
  "Substance": substance_name,
358
  "Valeur LMR": mrl.get("mrlValue", "N/C"),
 
364
  "Date d'approbation": self.format_date(getattr(substance, "approval_date", None)),
365
  "Date d'expiration": self.format_date(getattr(substance, "expiry_date", None))
366
  })
367
+
368
  df = pd.DataFrame(data)
369
  logger.info(f"Détails récupérés pour {product_name}: {len(df)} entrées")
370
  return df
 
374
  logger.info(f"Recherche de substances: {substance_query}")
375
  if not substance_query or len(substance_query) < 3:
376
  return pd.DataFrame([{"Message": "Veuillez entrer au moins 3 caractères pour la recherche"}])
377
+
378
  results = self.fetcher.search_substances(substance_query)
379
  if not results:
380
  return pd.DataFrame([{"Message": "Aucune substance trouvée"}])
381
+
382
  data = []
383
  for substance in results:
384
  data.append({
 
390
  "Date d'approbation": self.format_date(substance.approval_date),
391
  "Date d'expiration": self.format_date(substance.expiry_date)
392
  })
393
+
394
  df = pd.DataFrame(data)
395
  logger.info(f"Résultats de la recherche pour '{substance_query}': {len(df)} substances trouvées")
396
  return df
397
 
398
  def get_substance_mrls(self, substance_id: int) -> pd.DataFrame:
399
+ """Récupère tous les produits avec LMR pour une substance donnée et retourne un DataFrame."""
400
  logger.info(f"Récupération des LMR pour la substance ID: {substance_id}")
401
  if not substance_id:
402
  return pd.DataFrame([{"Erreur": "ID de substance non valide"}])
403
+
404
  substance = self.fetcher._substance_cache.get(substance_id)
405
  if not substance:
406
  return pd.DataFrame([{"Erreur": "Substance non trouvée"}])
407
+
408
+ all_mrls = self.fetcher.get_substance_mrls(substance_id) # Récupère la liste brute
409
+ if not all_mrls:
410
  return pd.DataFrame([{"Message": f"Aucune LMR trouvée pour {substance.name}"}])
411
+
412
+ # Conversion en DataFrame ici:
413
  data = []
414
+ for mrl in all_mrls:
415
  product_id = mrl.get("productId")
416
  product_name = "Inconnu"
417
+
418
  # Récupérer le nom du produit si possible
419
  if product_id in self.fetcher._product_cache:
420
  product_name = self.fetcher._product_cache[product_id].get("productName", "Inconnu")
421
+
422
  data.append({
423
  "Produit": product_name,
424
  "Valeur LMR": mrl.get("mrlValue", "N/C"),
 
426
  "Date d'effet": self.format_date(mrl.get("entryIntoForceDate")),
427
  "Notes": mrl.get("footnotes", "")
428
  })
429
+
430
+ df = pd.DataFrame(data) # Crée le DataFrame
431
  logger.info(f"LMR récupérées pour {substance.name}: {len(df)} entrées")
432
+ return df # MODIFICATION: retourner le dataframe
433
 
434
  def create_histogram(self, df: pd.DataFrame) -> go.Figure:
435
  """Crée un histogramme des valeurs LMR"""
 
437
  fig = go.Figure()
438
  fig.update_layout(title="Aucune donnée disponible pour l'histogramme")
439
  return fig
440
+
441
  # Convertir les valeurs LMR en nombres si possible
442
  numeric_values = []
443
  for val in df["Valeur LMR"]:
 
451
  numeric_values.append(float(val))
452
  except (ValueError, TypeError):
453
  continue
454
+
455
  if not numeric_values:
456
  fig = go.Figure()
457
  fig.update_layout(title="Aucune valeur numérique disponible pour l'histogramme")
458
  return fig
459
+
460
  # Création de l'histogramme
461
  fig = go.Figure(data=[go.Histogram(x=numeric_values, nbinsx=20)])
462
  fig.update_layout(
 
473
  fig = go.Figure()
474
  fig.update_layout(title=f"Aucune donnée disponible pour {column}")
475
  return fig
476
+
477
  # Compter les valeurs
478
  value_counts = df[column].value_counts()
479
+
480
  # Création du graphique
481
  fig = go.Figure(data=[go.Pie(
482
  labels=value_counts.index,
483
  values=value_counts.values
484
  )])
485
+
486
  fig.update_layout(title=f"Répartition par {column}")
487
  return fig
488
 
 
495
  <p>Consultez les limites maximales de résidus (LMR) de pesticides dans les produits alimentaires</p>
496
  </div>
497
  """)
498
+
499
  with gr.Tab("Recherche par Produit"):
500
  with gr.Row():
501
  with gr.Column(scale=3):
 
506
  )
507
  with gr.Column(scale=1):
508
  search_btn = gr.Button("Rechercher", variant="primary")
509
+
510
  with gr.Row():
511
  output = gr.Dataframe(
512
  headers=["Substance", "Valeur LMR", "Unité", "Date d'effet", "Statut"],
513
  max_rows=20,
514
  interactive=False
515
  )
516
+
517
  with gr.Row():
518
  with gr.Column():
519
  histogram = gr.Plot(label="Distribution des LMR")
520
  with gr.Column():
521
  status_pie = gr.Plot(label="Statut des substances")
522
+
523
  search_btn.click(
524
  fn=lambda p: (
525
  self.get_product_details(p),
 
529
  inputs=[product],
530
  outputs=[output, histogram, status_pie]
531
  )
532
+
533
  with gr.Tab("Recherche par Substance"):
534
  with gr.Row():
535
  with gr.Column(scale=3):
 
540
  )
541
  with gr.Column(scale=1):
542
  substance_search_btn = gr.Button("Rechercher", variant="primary")
543
+
544
  substance_results = gr.Dataframe(
545
  headers=["ID", "Nom", "Statut", "N° CAS", "N° EC", "Date d'approbation", "Date d'expiration"],
546
  max_rows=15,
547
  interactive=True
548
  )
549
+
550
  substance_select = gr.Number(label="ID de la substance sélectionnée", interactive=True, visible=False)
551
  substance_mrls_btn = gr.Button("Voir les LMR pour cette substance", visible=True)
552
+
553
  substance_mrls = gr.Dataframe(
554
  headers=["Produit", "Valeur LMR", "Unité", "Date d'effet", "Notes"],
555
  max_rows=20,
556
  interactive=False
557
  )
558
+
559
  mrl_histogram = gr.Plot(label="Distribution des LMR par produit")
560
+
561
  # Événements
562
  substance_search_btn.click(
563
  fn=self.search_substance,
564
  inputs=[substance_query],
565
  outputs=[substance_results]
566
  )
567
+
568
  # Mise à jour de l'ID de substance sélectionné
569
  substance_results.select(
570
  fn=lambda evt: evt["data"]["ID"] if evt and "data" in evt and "ID" in evt["data"] else None,
571
  inputs=[],
572
  outputs=[substance_select]
573
  )
574
+
575
  substance_mrls_btn.click(
576
  fn=lambda sid: (
577
+ self.get_substance_mrls(int(sid)), #MODIFICATION: int() conversion
578
+ self.create_histogram(self.get_substance_mrls(int(sid))) #MODIFICATION : int() conversion
579
  ),
580
  inputs=[substance_select],
581
  outputs=[substance_mrls, mrl_histogram]
582
  )
583
+
584
  with gr.Tab("À propos"):
585
  gr.HTML("""
586
  <div style="padding: 20px;">
587
  <h2>À propos de cette application</h2>
588
+ <p>Cette application permet de consulter les limites maximales de résidus (LMR) des pesticides
589
  autorisés dans les produits alimentaires dans l'Union européenne.</p>
590
+
591
  <h3>Sources de données</h3>
592
  <p>Les données sont extraites de l'API officielle de la Commission européenne:</p>
593
  <ul>
594
  <li>API Pesticides: <a href="https://api.datalake.sante.service.ec.europa.eu/sante/pesticides">
595
  https://api.datalake.sante.service.ec.europa.eu/sante/pesticides</a></li>
596
  </ul>
597
+
598
  <h3>Comment utiliser cette application</h3>
599
  <ul>
600
+ <li><strong>Recherche par Produit</strong>: Sélectionnez un produit alimentaire pour voir
601
  toutes les substances et leurs LMR associées.</li>
602
+ <li><strong>Recherche par Substance</strong>: Recherchez une substance active pour voir son
603
  statut et les produits dans lesquels elle est réglementée.</li>
604
  </ul>
605
+
606
  <h3>Légende</h3>
607
  <ul>
608
  <li><strong>LMR</strong>: Limite Maximale de Résidus, exprimée généralement en mg/kg</li>
 
611
  </ul>
612
  </div>
613
  """)
614
+
615
  return ui
616
 
617
  def main():
618
  # Paramètres de ligne de commande (vous pourriez les ajouter via argparse)
619
  use_cache = True # Utiliser le cache par défaut
620
+
621
  try:
622
  app = PesticideApp(use_cache=use_cache)
623
  logger.info("Lancement de l'interface utilisateur...")