Spaces:
Sleeping
Sleeping
MCP Server ADEME Environnement - Initial release
Browse filesServeur MCP exposant les données environnementales ADEME:
- AGRIBALYSE: impacts alimentaires (2500+ produits)
- Base Carbone: facteurs d'émission (20000+ FE)
9 outils MCP:
- search_emission_factors: recherche unifiée 2 bases
- get_emission_factor_details: détails méthodologiques
- compare_emission_factors: comparaison avec sensibilité
- search_food, get_transport/energy/waste/equipment_factors
- get_categories
Co-authored-by: Cursor <cursoragent@cursor.com>
- .env.example +5 -0
- .gitignore +4 -0
- README.md +148 -7
- app.py +163 -0
- requirements.txt +5 -0
- src/__init__.py +4 -0
- src/clients/__init__.py +4 -0
- src/clients/agribalyse.py +166 -0
- src/clients/base_carbone.py +278 -0
- src/models.py +143 -0
- src/tools.py +644 -0
.env.example
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Configuration MCP Server ADEME Environnement
|
| 2 |
+
# Pas de clé API requise - les APIs ADEME sont publiques
|
| 3 |
+
|
| 4 |
+
# Optionnel : timeout des requêtes HTTP (secondes)
|
| 5 |
+
# HTTP_TIMEOUT=30
|
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
venv/
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.pyc
|
README.md
CHANGED
|
@@ -1,13 +1,154 @@
|
|
| 1 |
---
|
| 2 |
-
title: MCP
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
-
short_description: MCP to acces api of ADEME Base Carbone et la Base Agribalyse
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: MCP Server ADEME Environnement
|
| 3 |
+
emoji: 🌱
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.29.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# 🌱 MCP Server ADEME Environnement
|
| 13 |
+
|
| 14 |
+
Serveur MCP (Model Context Protocol) pour accéder aux données environnementales de l'ADEME.
|
| 15 |
+
|
| 16 |
+
## 📊 Sources de données
|
| 17 |
+
|
| 18 |
+
| Base | Description | Données |
|
| 19 |
+
|------|-------------|---------|
|
| 20 |
+
| **AGRIBALYSE** | Impacts environnementaux alimentaires | 2500+ produits, 16 indicateurs |
|
| 21 |
+
| **Base Carbone** | Facteurs d'émission GES | 20000+ FE (transport, énergie, déchets, équipements) |
|
| 22 |
+
|
| 23 |
+
## 🛠️ Outils MCP disponibles
|
| 24 |
+
|
| 25 |
+
### Recherche
|
| 26 |
+
|
| 27 |
+
| Outil | Description |
|
| 28 |
+
|-------|-------------|
|
| 29 |
+
| `search_emission_factors` | Recherche unifiée dans AGRIBALYSE + Base Carbone |
|
| 30 |
+
| `get_emission_factor_details` | Détails méthodologiques complets d'un FE |
|
| 31 |
+
| `compare_emission_factors` | Comparaison de 2 FE avec analyse de sensibilité |
|
| 32 |
+
| `get_categories` | Liste des catégories disponibles |
|
| 33 |
+
|
| 34 |
+
### Par domaine
|
| 35 |
+
|
| 36 |
+
| Outil | Source | Description |
|
| 37 |
+
|-------|--------|-------------|
|
| 38 |
+
| `search_food` | AGRIBALYSE | Recherche d'aliments avec impacts environnementaux |
|
| 39 |
+
| `get_transport_factors` | Base Carbone | FE transport (véhicule, carburant) |
|
| 40 |
+
| `get_energy_factors` | Base Carbone | FE énergie (gaz, électricité, fioul) |
|
| 41 |
+
| `get_waste_factors` | Base Carbone | FE déchets (par type et traitement) |
|
| 42 |
+
| `get_equipment_factors` | Base Carbone | FE équipements (informatique, électroménager) |
|
| 43 |
+
|
| 44 |
+
## 🔌 Connexion MCP
|
| 45 |
+
|
| 46 |
+
### Configuration Claude Desktop
|
| 47 |
+
|
| 48 |
+
Ajouter dans `claude_desktop_config.json` :
|
| 49 |
+
|
| 50 |
+
```json
|
| 51 |
+
{
|
| 52 |
+
"mcpServers": {
|
| 53 |
+
"ademe-environnement": {
|
| 54 |
+
"url": "https://VOTRE-USERNAME-mcp-ademe-environnement.hf.space/gradio_api/mcp/sse"
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### Configuration Cursor
|
| 61 |
+
|
| 62 |
+
Ajouter dans les paramètres MCP :
|
| 63 |
+
|
| 64 |
+
```json
|
| 65 |
+
{
|
| 66 |
+
"ademe-environnement": {
|
| 67 |
+
"url": "https://VOTRE-USERNAME-mcp-ademe-environnement.hf.space/gradio_api/mcp/sse"
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
## 📖 Exemples d'utilisation
|
| 73 |
+
|
| 74 |
+
### Recherche unifiée
|
| 75 |
+
|
| 76 |
+
```
|
| 77 |
+
search_emission_factors("boeuf")
|
| 78 |
+
→ Résultats AGRIBALYSE (viande) + Base Carbone (élevage)
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
### Analyse de sensibilité transport
|
| 82 |
+
|
| 83 |
+
```
|
| 84 |
+
# Trouver les FE
|
| 85 |
+
get_transport_factors("VUL", "diesel")
|
| 86 |
+
get_transport_factors("VUL", "électrique")
|
| 87 |
+
|
| 88 |
+
# Comparer
|
| 89 |
+
compare_emission_factors("28022", "28024")
|
| 90 |
+
→ Écart, incertitude, recommandation
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
### Bilan repas cantine
|
| 94 |
+
|
| 95 |
+
```
|
| 96 |
+
# Aliments
|
| 97 |
+
search_food("poulet rôti")
|
| 98 |
+
search_food("haricots verts")
|
| 99 |
+
|
| 100 |
+
# Transport
|
| 101 |
+
get_transport_factors("VUL", "diesel")
|
| 102 |
+
|
| 103 |
+
# Énergie cuisson
|
| 104 |
+
get_energy_factors("gaz naturel")
|
| 105 |
+
|
| 106 |
+
# Déchets
|
| 107 |
+
get_waste_factors("alimentaire", "compost")
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
## 📁 Structure du projet
|
| 111 |
+
|
| 112 |
+
```
|
| 113 |
+
mcp-ademe-environnement/
|
| 114 |
+
├── app.py # Point d'entrée Gradio + MCP
|
| 115 |
+
├── requirements.txt # Dépendances
|
| 116 |
+
├── README.md # Ce fichier
|
| 117 |
+
└── src/
|
| 118 |
+
├── __init__.py
|
| 119 |
+
├── models.py # Modèles Pydantic
|
| 120 |
+
├── tools.py # Outils MCP
|
| 121 |
+
└── clients/
|
| 122 |
+
├── __init__.py
|
| 123 |
+
├── agribalyse.py # Client API AGRIBALYSE
|
| 124 |
+
└── base_carbone.py # Client API Base Carbone
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
## 🚀 Déploiement
|
| 128 |
+
|
| 129 |
+
### Local
|
| 130 |
+
|
| 131 |
+
```bash
|
| 132 |
+
pip install -r requirements.txt
|
| 133 |
+
python app.py
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
### HuggingFace Spaces
|
| 137 |
+
|
| 138 |
+
1. Créer un nouveau Space (SDK: Gradio)
|
| 139 |
+
2. Pousser le code
|
| 140 |
+
3. Le Space démarre automatiquement
|
| 141 |
+
|
| 142 |
+
## 📚 Documentation des APIs
|
| 143 |
+
|
| 144 |
+
- [AGRIBALYSE](https://agribalyse.ademe.fr/) - Documentation officielle
|
| 145 |
+
- [Base Carbone](https://base-empreinte.ademe.fr/) - Base Empreinte ADEME
|
| 146 |
+
- [data.ademe.fr](https://data.ademe.fr/) - Portail open data ADEME
|
| 147 |
+
|
| 148 |
+
## 📄 Licence
|
| 149 |
+
|
| 150 |
+
Données sous [Licence Ouverte / Open Licence](https://www.etalab.gouv.fr/licence-ouverte-open-licence/)
|
| 151 |
+
|
| 152 |
+
---
|
| 153 |
+
|
| 154 |
+
Développé avec ❤️ pour faciliter l'accès aux données environnementales
|
app.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MCP Server ADEME Environnement - Point d'entrée Gradio.
|
| 2 |
+
|
| 3 |
+
Ce serveur MCP expose les données environnementales de l'ADEME :
|
| 4 |
+
- AGRIBALYSE : impacts environnementaux de 2500+ aliments
|
| 5 |
+
- Base Carbone : 20000+ facteurs d'émission (transport, énergie, déchets, équipements)
|
| 6 |
+
|
| 7 |
+
Utilisation :
|
| 8 |
+
- Déployer sur HuggingFace Spaces
|
| 9 |
+
- Connecter depuis un client MCP (Claude, etc.)
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import gradio as gr
|
| 13 |
+
|
| 14 |
+
from src.tools import (
|
| 15 |
+
search_emission_factors,
|
| 16 |
+
get_emission_factor_details,
|
| 17 |
+
compare_emission_factors,
|
| 18 |
+
search_food,
|
| 19 |
+
get_transport_factors,
|
| 20 |
+
get_energy_factors,
|
| 21 |
+
get_waste_factors,
|
| 22 |
+
get_equipment_factors,
|
| 23 |
+
get_categories,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# Créer les interfaces pour chaque outil MCP
|
| 28 |
+
# Chaque gr.Interface expose la fonction comme outil MCP
|
| 29 |
+
|
| 30 |
+
search_interface = gr.Interface(
|
| 31 |
+
fn=search_emission_factors,
|
| 32 |
+
inputs=[
|
| 33 |
+
gr.Textbox(label="query", info="Terme de recherche (ex: boeuf, camion diesel)"),
|
| 34 |
+
gr.Dropdown(choices=["all", "agribalyse", "base_carbone"], value="all", label="source"),
|
| 35 |
+
gr.Number(value=10, label="size", info="Nombre de résultats"),
|
| 36 |
+
],
|
| 37 |
+
outputs=gr.JSON(label="Résultats"),
|
| 38 |
+
title="search_emission_factors",
|
| 39 |
+
description="Recherche unifiée de facteurs d'émission dans AGRIBALYSE et Base Carbone",
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
details_interface = gr.Interface(
|
| 43 |
+
fn=get_emission_factor_details,
|
| 44 |
+
inputs=[
|
| 45 |
+
gr.Textbox(label="identifier", info="ID du facteur d'émission"),
|
| 46 |
+
gr.Dropdown(choices=["base_carbone", "agribalyse"], value="base_carbone", label="source"),
|
| 47 |
+
],
|
| 48 |
+
outputs=gr.JSON(label="Détails"),
|
| 49 |
+
title="get_emission_factor_details",
|
| 50 |
+
description="Détails méthodologiques complets d'un facteur d'émission",
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
compare_interface = gr.Interface(
|
| 54 |
+
fn=compare_emission_factors,
|
| 55 |
+
inputs=[
|
| 56 |
+
gr.Textbox(label="id1", info="ID du premier FE"),
|
| 57 |
+
gr.Textbox(label="id2", info="ID du second FE"),
|
| 58 |
+
gr.Dropdown(choices=["base_carbone", "agribalyse"], value="base_carbone", label="source1"),
|
| 59 |
+
gr.Dropdown(choices=["base_carbone", "agribalyse"], value="base_carbone", label="source2"),
|
| 60 |
+
],
|
| 61 |
+
outputs=gr.JSON(label="Comparaison"),
|
| 62 |
+
title="compare_emission_factors",
|
| 63 |
+
description="Compare deux facteurs d'émission avec analyse de sensibilité",
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
food_interface = gr.Interface(
|
| 67 |
+
fn=search_food,
|
| 68 |
+
inputs=[
|
| 69 |
+
gr.Textbox(label="query", info="Terme de recherche (ex: steak, yaourt)"),
|
| 70 |
+
gr.Textbox(label="group", info="Groupe d'aliment (optionnel)"),
|
| 71 |
+
gr.Number(value=10, label="size"),
|
| 72 |
+
],
|
| 73 |
+
outputs=gr.JSON(label="Résultats"),
|
| 74 |
+
title="search_food",
|
| 75 |
+
description="Recherche d'aliments dans la base AGRIBALYSE",
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
transport_interface = gr.Interface(
|
| 79 |
+
fn=get_transport_factors,
|
| 80 |
+
inputs=[
|
| 81 |
+
gr.Textbox(label="vehicle_type", info="Type de véhicule (ex: VUL, poids lourd)"),
|
| 82 |
+
gr.Textbox(label="fuel_type", info="Carburant (ex: diesel, électrique)"),
|
| 83 |
+
gr.Number(value=20, label="size"),
|
| 84 |
+
],
|
| 85 |
+
outputs=gr.JSON(label="Résultats"),
|
| 86 |
+
title="get_transport_factors",
|
| 87 |
+
description="Recherche de facteurs d'émission pour le transport",
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
energy_interface = gr.Interface(
|
| 91 |
+
fn=get_energy_factors,
|
| 92 |
+
inputs=[
|
| 93 |
+
gr.Textbox(label="energy_type", info="Type d'énergie (ex: gaz naturel, électricité)"),
|
| 94 |
+
gr.Number(value=20, label="size"),
|
| 95 |
+
],
|
| 96 |
+
outputs=gr.JSON(label="Résultats"),
|
| 97 |
+
title="get_energy_factors",
|
| 98 |
+
description="Recherche de facteurs d'émission pour l'énergie",
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
waste_interface = gr.Interface(
|
| 102 |
+
fn=get_waste_factors,
|
| 103 |
+
inputs=[
|
| 104 |
+
gr.Textbox(label="waste_type", info="Type de déchet (ex: alimentaire, plastique)"),
|
| 105 |
+
gr.Textbox(label="treatment", info="Traitement (ex: compost, incinération)"),
|
| 106 |
+
gr.Number(value=20, label="size"),
|
| 107 |
+
],
|
| 108 |
+
outputs=gr.JSON(label="Résultats"),
|
| 109 |
+
title="get_waste_factors",
|
| 110 |
+
description="Recherche de facteurs d'émission pour les déchets",
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
equipment_interface = gr.Interface(
|
| 114 |
+
fn=get_equipment_factors,
|
| 115 |
+
inputs=[
|
| 116 |
+
gr.Textbox(label="equipment_type", info="Type d'équipement (ex: ordinateur, smartphone)"),
|
| 117 |
+
gr.Number(value=20, label="size"),
|
| 118 |
+
],
|
| 119 |
+
outputs=gr.JSON(label="Résultats"),
|
| 120 |
+
title="get_equipment_factors",
|
| 121 |
+
description="Recherche de facteurs d'émission pour les équipements",
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
categories_interface = gr.Interface(
|
| 125 |
+
fn=get_categories,
|
| 126 |
+
inputs=[
|
| 127 |
+
gr.Dropdown(choices=["all", "agribalyse", "base_carbone"], value="all", label="source"),
|
| 128 |
+
],
|
| 129 |
+
outputs=gr.JSON(label="Catégories"),
|
| 130 |
+
title="get_categories",
|
| 131 |
+
description="Liste les catégories disponibles dans les bases de données",
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
# Combiner toutes les interfaces dans des onglets
|
| 135 |
+
demo = gr.TabbedInterface(
|
| 136 |
+
interface_list=[
|
| 137 |
+
search_interface,
|
| 138 |
+
food_interface,
|
| 139 |
+
transport_interface,
|
| 140 |
+
energy_interface,
|
| 141 |
+
waste_interface,
|
| 142 |
+
equipment_interface,
|
| 143 |
+
compare_interface,
|
| 144 |
+
details_interface,
|
| 145 |
+
categories_interface,
|
| 146 |
+
],
|
| 147 |
+
tab_names=[
|
| 148 |
+
"🔍 Recherche",
|
| 149 |
+
"🍎 Alimentation",
|
| 150 |
+
"🚗 Transport",
|
| 151 |
+
"⚡ Énergie",
|
| 152 |
+
"🗑️ Déchets",
|
| 153 |
+
"💻 Équipements",
|
| 154 |
+
"⚖️ Comparer",
|
| 155 |
+
"📋 Détails",
|
| 156 |
+
"📂 Catégories",
|
| 157 |
+
],
|
| 158 |
+
title="🌱 MCP Server ADEME Environnement",
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
# Lancement avec MCP activé
|
| 162 |
+
if __name__ == "__main__":
|
| 163 |
+
demo.launch(mcp_server=True)
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio[mcp]>=5.27.1
|
| 2 |
+
httpx>=0.27.0
|
| 3 |
+
pydantic>=2.0.0
|
| 4 |
+
python-dotenv>=1.0.0
|
| 5 |
+
anyio>=4.0.0
|
src/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MCP Server ADEME Environnement
|
| 2 |
+
# Intégration AGRIBALYSE + Base Carbone
|
| 3 |
+
|
| 4 |
+
__version__ = "1.0.0"
|
src/clients/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .agribalyse import AgribalyseClient
|
| 2 |
+
from .base_carbone import BaseCarboneClient
|
| 3 |
+
|
| 4 |
+
__all__ = ["AgribalyseClient", "BaseCarboneClient"]
|
src/clients/agribalyse.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Client API AGRIBALYSE - Données environnementales alimentaires."""
|
| 2 |
+
|
| 3 |
+
import httpx
|
| 4 |
+
from typing import Any
|
| 5 |
+
|
| 6 |
+
BASE_URL = "https://data.ademe.fr/data-fair/api/v1/datasets/agribalyse-31-synthese"
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class AgribalyseClient:
|
| 10 |
+
"""Client async pour l'API AGRIBALYSE."""
|
| 11 |
+
|
| 12 |
+
def __init__(self, timeout: float = 30.0):
|
| 13 |
+
self.timeout = timeout
|
| 14 |
+
self._client: httpx.AsyncClient | None = None
|
| 15 |
+
|
| 16 |
+
async def _get_client(self) -> httpx.AsyncClient:
|
| 17 |
+
if self._client is None:
|
| 18 |
+
self._client = httpx.AsyncClient(timeout=self.timeout)
|
| 19 |
+
return self._client
|
| 20 |
+
|
| 21 |
+
async def close(self):
|
| 22 |
+
if self._client:
|
| 23 |
+
await self._client.aclose()
|
| 24 |
+
self._client = None
|
| 25 |
+
|
| 26 |
+
async def search(
|
| 27 |
+
self,
|
| 28 |
+
query: str,
|
| 29 |
+
size: int = 10,
|
| 30 |
+
select: list[str] | None = None
|
| 31 |
+
) -> dict[str, Any]:
|
| 32 |
+
"""Recherche d'aliments dans AGRIBALYSE.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
query: Terme de recherche (ex: "boeuf", "pomme")
|
| 36 |
+
size: Nombre de résultats (max 100)
|
| 37 |
+
select: Champs à retourner (None = tous)
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
Dict avec total et results
|
| 41 |
+
"""
|
| 42 |
+
client = await self._get_client()
|
| 43 |
+
|
| 44 |
+
params = {
|
| 45 |
+
"q": query,
|
| 46 |
+
"size": min(size, 100)
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
if select:
|
| 50 |
+
params["select"] = ",".join(select)
|
| 51 |
+
|
| 52 |
+
response = await client.get(f"{BASE_URL}/lines", params=params)
|
| 53 |
+
response.raise_for_status()
|
| 54 |
+
return response.json()
|
| 55 |
+
|
| 56 |
+
async def get_by_code(self, code_ciqual: int) -> dict[str, Any] | None:
|
| 57 |
+
"""Récupère un aliment par son code CIQUAL.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
code_ciqual: Code CIQUAL de l'aliment
|
| 61 |
+
|
| 62 |
+
Returns:
|
| 63 |
+
Données de l'aliment ou None
|
| 64 |
+
"""
|
| 65 |
+
client = await self._get_client()
|
| 66 |
+
|
| 67 |
+
params = {
|
| 68 |
+
"qs": f"Code_CIQUAL:{code_ciqual}",
|
| 69 |
+
"size": 1
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
response = await client.get(f"{BASE_URL}/lines", params=params)
|
| 73 |
+
response.raise_for_status()
|
| 74 |
+
data = response.json()
|
| 75 |
+
|
| 76 |
+
if data.get("results"):
|
| 77 |
+
return data["results"][0]
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
+
async def get_categories(self) -> list[str]:
|
| 81 |
+
"""Liste les groupes d'aliments disponibles.
|
| 82 |
+
|
| 83 |
+
Returns:
|
| 84 |
+
Liste des groupes d'aliments uniques
|
| 85 |
+
"""
|
| 86 |
+
client = await self._get_client()
|
| 87 |
+
|
| 88 |
+
response = await client.get(
|
| 89 |
+
f"{BASE_URL}/values_agg",
|
| 90 |
+
params={"field": "Groupe_d'aliment", "size": 100}
|
| 91 |
+
)
|
| 92 |
+
response.raise_for_status()
|
| 93 |
+
data = response.json()
|
| 94 |
+
|
| 95 |
+
return [item["value"] for item in data.get("aggs", [])]
|
| 96 |
+
|
| 97 |
+
async def get_subcategories(self, group: str) -> list[str]:
|
| 98 |
+
"""Liste les sous-groupes d'un groupe d'aliments.
|
| 99 |
+
|
| 100 |
+
Args:
|
| 101 |
+
group: Nom du groupe d'aliments
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
Liste des sous-groupes
|
| 105 |
+
"""
|
| 106 |
+
client = await self._get_client()
|
| 107 |
+
|
| 108 |
+
response = await client.get(
|
| 109 |
+
f"{BASE_URL}/values_agg",
|
| 110 |
+
params={
|
| 111 |
+
"field": "Sous-groupe_d'aliment",
|
| 112 |
+
"size": 100,
|
| 113 |
+
"qs": f"Groupe_d'aliment:\"{group}\""
|
| 114 |
+
}
|
| 115 |
+
)
|
| 116 |
+
response.raise_for_status()
|
| 117 |
+
data = response.json()
|
| 118 |
+
|
| 119 |
+
return [item["value"] for item in data.get("aggs", [])]
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
# Champs principaux AGRIBALYSE pour référence
|
| 123 |
+
AGRIBALYSE_FIELDS = {
|
| 124 |
+
"identite": [
|
| 125 |
+
"Code_CIQUAL",
|
| 126 |
+
"Code_AGB",
|
| 127 |
+
"Nom_du_Produit_en_Français",
|
| 128 |
+
"LCI_Name",
|
| 129 |
+
"Groupe_d'aliment",
|
| 130 |
+
"Sous-groupe_d'aliment",
|
| 131 |
+
],
|
| 132 |
+
"impact_climatique": [
|
| 133 |
+
"Changement_climatique",
|
| 134 |
+
"Changement_climatique_-_émissions_fossiles",
|
| 135 |
+
"Changement_climatique_-_émissions_biogéniques",
|
| 136 |
+
"Changement_climatique_-_émissions_liées_au_changement_d'affectation_des_sols",
|
| 137 |
+
],
|
| 138 |
+
"autres_impacts": [
|
| 139 |
+
"Score_unique_EF",
|
| 140 |
+
"Acidification_terrestre_et_eaux_douces",
|
| 141 |
+
"Eutrophisation_marine",
|
| 142 |
+
"Eutrophisation_eaux_douces",
|
| 143 |
+
"Eutrophisation_terrestre",
|
| 144 |
+
"Utilisation_du_sol",
|
| 145 |
+
"Épuisement_des_ressources_eau",
|
| 146 |
+
"Épuisement_des_ressources_énergétiques",
|
| 147 |
+
"Épuisement_des_ressources_minéraux",
|
| 148 |
+
"Particules_fines",
|
| 149 |
+
"Formation_photochimique_d'ozone",
|
| 150 |
+
"Appauvrissement_de_la_couche_d'ozone",
|
| 151 |
+
"Rayonnements_ionisants",
|
| 152 |
+
"Écotoxicité_pour_écosystèmes_aquatiques_d'eau_douce",
|
| 153 |
+
"Effets_toxicologiques_sur_la_santé_humaine___substances_cancérogènes",
|
| 154 |
+
"Effets_toxicologiques_sur_la_santé_humaine___substances_non-cancérogènes",
|
| 155 |
+
],
|
| 156 |
+
"qualite": [
|
| 157 |
+
"DQR",
|
| 158 |
+
],
|
| 159 |
+
"contexte": [
|
| 160 |
+
"Livraison",
|
| 161 |
+
"Préparation",
|
| 162 |
+
"Approche_emballage_",
|
| 163 |
+
"code_avion",
|
| 164 |
+
"code_saison",
|
| 165 |
+
],
|
| 166 |
+
}
|
src/clients/base_carbone.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Client API Base Carbone - Facteurs d'émission ADEME."""
|
| 2 |
+
|
| 3 |
+
import httpx
|
| 4 |
+
from typing import Any
|
| 5 |
+
|
| 6 |
+
BASE_URL = "https://data.ademe.fr/data-fair/api/v1/datasets/base-carboner"
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class BaseCarboneClient:
|
| 10 |
+
"""Client async pour l'API Base Carbone."""
|
| 11 |
+
|
| 12 |
+
def __init__(self, timeout: float = 30.0):
|
| 13 |
+
self.timeout = timeout
|
| 14 |
+
self._client: httpx.AsyncClient | None = None
|
| 15 |
+
|
| 16 |
+
async def _get_client(self) -> httpx.AsyncClient:
|
| 17 |
+
if self._client is None:
|
| 18 |
+
self._client = httpx.AsyncClient(timeout=self.timeout)
|
| 19 |
+
return self._client
|
| 20 |
+
|
| 21 |
+
async def close(self):
|
| 22 |
+
if self._client:
|
| 23 |
+
await self._client.aclose()
|
| 24 |
+
self._client = None
|
| 25 |
+
|
| 26 |
+
async def search(
|
| 27 |
+
self,
|
| 28 |
+
query: str,
|
| 29 |
+
size: int = 10,
|
| 30 |
+
type_ligne: str = "Elément",
|
| 31 |
+
statut: str | None = None,
|
| 32 |
+
category: str | None = None
|
| 33 |
+
) -> dict[str, Any]:
|
| 34 |
+
"""Recherche de facteurs d'émission dans Base Carbone.
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
query: Terme de recherche
|
| 38 |
+
size: Nombre de résultats (max 100)
|
| 39 |
+
type_ligne: "Elément" ou "Poste" (défaut: Elément)
|
| 40 |
+
statut: Filtrer par statut ("Valide générique", "Archivé", etc.)
|
| 41 |
+
category: Filtrer par catégorie (chemin partiel)
|
| 42 |
+
|
| 43 |
+
Returns:
|
| 44 |
+
Dict avec total et results
|
| 45 |
+
"""
|
| 46 |
+
client = await self._get_client()
|
| 47 |
+
|
| 48 |
+
params = {
|
| 49 |
+
"q": query,
|
| 50 |
+
"size": min(size, 100)
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
# Construire les filtres
|
| 54 |
+
filters = []
|
| 55 |
+
if type_ligne:
|
| 56 |
+
filters.append(f"Type_Ligne:{type_ligne}")
|
| 57 |
+
if statut:
|
| 58 |
+
filters.append(f"Statut_de_l'élément:\"{statut}\"")
|
| 59 |
+
if category:
|
| 60 |
+
filters.append(f"Code_de_la_catégorie:*{category}*")
|
| 61 |
+
|
| 62 |
+
if filters:
|
| 63 |
+
params["qs"] = " AND ".join(filters)
|
| 64 |
+
|
| 65 |
+
response = await client.get(f"{BASE_URL}/lines", params=params)
|
| 66 |
+
response.raise_for_status()
|
| 67 |
+
return response.json()
|
| 68 |
+
|
| 69 |
+
async def get_by_id(self, element_id: str) -> dict[str, Any] | None:
|
| 70 |
+
"""Récupère un facteur d'émission par son identifiant.
|
| 71 |
+
|
| 72 |
+
Args:
|
| 73 |
+
element_id: Identifiant de l'élément
|
| 74 |
+
|
| 75 |
+
Returns:
|
| 76 |
+
Données du FE ou None
|
| 77 |
+
"""
|
| 78 |
+
client = await self._get_client()
|
| 79 |
+
|
| 80 |
+
params = {
|
| 81 |
+
"qs": f"Identifiant_de_l'élément:{element_id}",
|
| 82 |
+
"size": 10 # Peut avoir plusieurs lignes (postes)
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
response = await client.get(f"{BASE_URL}/lines", params=params)
|
| 86 |
+
response.raise_for_status()
|
| 87 |
+
data = response.json()
|
| 88 |
+
|
| 89 |
+
if data.get("results"):
|
| 90 |
+
return data["results"]
|
| 91 |
+
return None
|
| 92 |
+
|
| 93 |
+
async def get_categories(self, parent: str | None = None) -> list[dict[str, Any]]:
|
| 94 |
+
"""Liste les catégories disponibles.
|
| 95 |
+
|
| 96 |
+
Args:
|
| 97 |
+
parent: Chemin de catégorie parent pour filtrer
|
| 98 |
+
|
| 99 |
+
Returns:
|
| 100 |
+
Liste des catégories avec leur compte
|
| 101 |
+
"""
|
| 102 |
+
client = await self._get_client()
|
| 103 |
+
|
| 104 |
+
params = {
|
| 105 |
+
"field": "Code_de_la_catégorie",
|
| 106 |
+
"size": 200
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
if parent:
|
| 110 |
+
params["qs"] = f"Code_de_la_catégorie:*{parent}*"
|
| 111 |
+
|
| 112 |
+
response = await client.get(f"{BASE_URL}/values_agg", params=params)
|
| 113 |
+
response.raise_for_status()
|
| 114 |
+
data = response.json()
|
| 115 |
+
|
| 116 |
+
return [
|
| 117 |
+
{"category": item["value"], "count": item["total"]}
|
| 118 |
+
for item in data.get("aggs", [])
|
| 119 |
+
]
|
| 120 |
+
|
| 121 |
+
async def search_transport(
|
| 122 |
+
self,
|
| 123 |
+
vehicle_type: str | None = None,
|
| 124 |
+
fuel_type: str | None = None,
|
| 125 |
+
size: int = 20
|
| 126 |
+
) -> dict[str, Any]:
|
| 127 |
+
"""Recherche de FE transport.
|
| 128 |
+
|
| 129 |
+
Args:
|
| 130 |
+
vehicle_type: Type de véhicule (VUL, Poids lourd, Voiture, etc.)
|
| 131 |
+
fuel_type: Type de carburant (Diesel, Essence, Électrique, etc.)
|
| 132 |
+
size: Nombre de résultats
|
| 133 |
+
|
| 134 |
+
Returns:
|
| 135 |
+
Facteurs d'émission transport
|
| 136 |
+
"""
|
| 137 |
+
query_parts = ["transport"]
|
| 138 |
+
if vehicle_type:
|
| 139 |
+
query_parts.append(vehicle_type)
|
| 140 |
+
if fuel_type:
|
| 141 |
+
query_parts.append(fuel_type)
|
| 142 |
+
|
| 143 |
+
return await self.search(
|
| 144 |
+
query=" ".join(query_parts),
|
| 145 |
+
size=size,
|
| 146 |
+
category="Transport"
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
async def search_energy(
|
| 150 |
+
self,
|
| 151 |
+
energy_type: str | None = None,
|
| 152 |
+
size: int = 20
|
| 153 |
+
) -> dict[str, Any]:
|
| 154 |
+
"""Recherche de FE énergie.
|
| 155 |
+
|
| 156 |
+
Args:
|
| 157 |
+
energy_type: Type d'énergie (Gaz naturel, Électricité, Fioul, etc.)
|
| 158 |
+
size: Nombre de résultats
|
| 159 |
+
|
| 160 |
+
Returns:
|
| 161 |
+
Facteurs d'émission énergie
|
| 162 |
+
"""
|
| 163 |
+
query = energy_type or "énergie"
|
| 164 |
+
|
| 165 |
+
return await self.search(
|
| 166 |
+
query=query,
|
| 167 |
+
size=size,
|
| 168 |
+
category="Combustibles"
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
async def search_waste(
|
| 172 |
+
self,
|
| 173 |
+
waste_type: str | None = None,
|
| 174 |
+
treatment: str | None = None,
|
| 175 |
+
size: int = 20
|
| 176 |
+
) -> dict[str, Any]:
|
| 177 |
+
"""Recherche de FE déchets.
|
| 178 |
+
|
| 179 |
+
Args:
|
| 180 |
+
waste_type: Type de déchet (alimentaire, plastique, etc.)
|
| 181 |
+
treatment: Traitement (compost, incinération, stockage, etc.)
|
| 182 |
+
size: Nombre de résultats
|
| 183 |
+
|
| 184 |
+
Returns:
|
| 185 |
+
Facteurs d'émission déchets
|
| 186 |
+
"""
|
| 187 |
+
query_parts = ["déchet"]
|
| 188 |
+
if waste_type:
|
| 189 |
+
query_parts.append(waste_type)
|
| 190 |
+
if treatment:
|
| 191 |
+
query_parts.append(treatment)
|
| 192 |
+
|
| 193 |
+
return await self.search(
|
| 194 |
+
query=" ".join(query_parts),
|
| 195 |
+
size=size,
|
| 196 |
+
category="Traitement des déchets"
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
async def search_equipment(
|
| 200 |
+
self,
|
| 201 |
+
equipment_type: str | None = None,
|
| 202 |
+
size: int = 20
|
| 203 |
+
) -> dict[str, Any]:
|
| 204 |
+
"""Recherche de FE équipements.
|
| 205 |
+
|
| 206 |
+
Args:
|
| 207 |
+
equipment_type: Type d'équipement (ordinateur, téléphone, etc.)
|
| 208 |
+
size: Nombre de résultats
|
| 209 |
+
|
| 210 |
+
Returns:
|
| 211 |
+
Facteurs d'émission équipements
|
| 212 |
+
"""
|
| 213 |
+
query = equipment_type or "équipement informatique"
|
| 214 |
+
|
| 215 |
+
return await self.search(
|
| 216 |
+
query=query,
|
| 217 |
+
size=size,
|
| 218 |
+
category="Achats de biens"
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
# Champs principaux Base Carbone pour référence
|
| 223 |
+
BASE_CARBONE_FIELDS = {
|
| 224 |
+
"identite": [
|
| 225 |
+
"Identifiant_de_l'élément",
|
| 226 |
+
"Nom_base_français",
|
| 227 |
+
"Nom_base_anglais",
|
| 228 |
+
"Nom_attribut_français",
|
| 229 |
+
"Nom_frontière_français",
|
| 230 |
+
"Code_de_la_catégorie",
|
| 231 |
+
"Type_de_l'élément",
|
| 232 |
+
"Type_Ligne",
|
| 233 |
+
],
|
| 234 |
+
"valeurs": [
|
| 235 |
+
"Total_poste_non_décomposé",
|
| 236 |
+
"Unité_français",
|
| 237 |
+
"Unité_anglais",
|
| 238 |
+
],
|
| 239 |
+
"decomposition_gaz": [
|
| 240 |
+
"CO2f",
|
| 241 |
+
"CO2b",
|
| 242 |
+
"CH4f",
|
| 243 |
+
"CH4b",
|
| 244 |
+
"N2O",
|
| 245 |
+
"Autres_GES",
|
| 246 |
+
],
|
| 247 |
+
"qualite": [
|
| 248 |
+
"Incertitude",
|
| 249 |
+
"Qualité",
|
| 250 |
+
"Qualité_M",
|
| 251 |
+
"Qualité_TeR",
|
| 252 |
+
"Qualité_C",
|
| 253 |
+
"Qualité_TiR",
|
| 254 |
+
"Qualité_GR",
|
| 255 |
+
"Qualité_P",
|
| 256 |
+
"Transparence",
|
| 257 |
+
],
|
| 258 |
+
"methodologie": [
|
| 259 |
+
"Source",
|
| 260 |
+
"Programme",
|
| 261 |
+
"Url_du_programme",
|
| 262 |
+
"Contributeur",
|
| 263 |
+
"Autres_Contributeurs",
|
| 264 |
+
"Structure",
|
| 265 |
+
"Commentaire_français",
|
| 266 |
+
"Réglementations",
|
| 267 |
+
],
|
| 268 |
+
"validite": [
|
| 269 |
+
"Statut_de_l'élément",
|
| 270 |
+
"Date_de_création",
|
| 271 |
+
"Date_de_modification",
|
| 272 |
+
"Période_de_validité",
|
| 273 |
+
],
|
| 274 |
+
"localisation": [
|
| 275 |
+
"Localisation_géographique",
|
| 276 |
+
"Sous-localisation_géographique_français",
|
| 277 |
+
],
|
| 278 |
+
}
|
src/models.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Modèles Pydantic pour le MCP Server ADEME."""
|
| 2 |
+
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
+
from typing import Any
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class EmissionFactor(BaseModel):
|
| 8 |
+
"""Facteur d'émission unifié (AGRIBALYSE ou Base Carbone)."""
|
| 9 |
+
|
| 10 |
+
source: str = Field(description="Source: 'agribalyse' ou 'base_carbone'")
|
| 11 |
+
id: str = Field(description="Identifiant unique")
|
| 12 |
+
name: str = Field(description="Nom en français")
|
| 13 |
+
value: float = Field(description="Valeur du FE en kgCO2e")
|
| 14 |
+
unit: str = Field(description="Unité (kgCO2e/kg, kgCO2e/km, etc.)")
|
| 15 |
+
category: str = Field(description="Catégorie")
|
| 16 |
+
|
| 17 |
+
# Qualité
|
| 18 |
+
uncertainty: float | None = Field(default=None, description="Incertitude en %")
|
| 19 |
+
dqr: float | None = Field(default=None, description="Data Quality Rating (1-5)")
|
| 20 |
+
|
| 21 |
+
# Métadonnées
|
| 22 |
+
methodology_source: str | None = Field(default=None, description="Source méthodologique")
|
| 23 |
+
validity_period: str | None = Field(default=None, description="Période de validité")
|
| 24 |
+
status: str | None = Field(default=None, description="Statut (Valide, Archivé)")
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class FoodItem(BaseModel):
|
| 28 |
+
"""Aliment AGRIBALYSE avec impacts environnementaux."""
|
| 29 |
+
|
| 30 |
+
code_ciqual: int = Field(description="Code CIQUAL")
|
| 31 |
+
code_agb: str = Field(description="Code AGRIBALYSE")
|
| 32 |
+
name_fr: str = Field(description="Nom en français")
|
| 33 |
+
name_en: str | None = Field(default=None, description="Nom en anglais")
|
| 34 |
+
|
| 35 |
+
group: str = Field(description="Groupe d'aliment")
|
| 36 |
+
subgroup: str = Field(description="Sous-groupe d'aliment")
|
| 37 |
+
|
| 38 |
+
# Impact climatique
|
| 39 |
+
climate_change: float = Field(description="Impact changement climatique (kgCO2e/kg)")
|
| 40 |
+
climate_change_fossil: float | None = Field(default=None, description="Émissions fossiles")
|
| 41 |
+
climate_change_biogenic: float | None = Field(default=None, description="Émissions biogéniques")
|
| 42 |
+
climate_change_land_use: float | None = Field(default=None, description="Changement d'affectation des sols")
|
| 43 |
+
|
| 44 |
+
# Score global
|
| 45 |
+
ef_score: float | None = Field(default=None, description="Score unique EF")
|
| 46 |
+
dqr: float | None = Field(default=None, description="Data Quality Rating")
|
| 47 |
+
|
| 48 |
+
# Contexte
|
| 49 |
+
delivery: str | None = Field(default=None, description="Mode de livraison")
|
| 50 |
+
preparation: str | None = Field(default=None, description="Mode de préparation")
|
| 51 |
+
packaging: str | None = Field(default=None, description="Type d'emballage")
|
| 52 |
+
by_plane: bool = Field(default=False, description="Transport par avion")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class TransportFactor(BaseModel):
|
| 56 |
+
"""Facteur d'émission transport."""
|
| 57 |
+
|
| 58 |
+
id: str
|
| 59 |
+
vehicle_type: str = Field(description="Type de véhicule")
|
| 60 |
+
fuel_type: str | None = Field(default=None, description="Type de carburant")
|
| 61 |
+
attribute: str | None = Field(default=None, description="Attribut (tonnage, etc.)")
|
| 62 |
+
|
| 63 |
+
value: float = Field(description="Valeur en kgCO2e")
|
| 64 |
+
unit: str = Field(description="Unité (kgCO2e/km, kgCO2e/t.km, etc.)")
|
| 65 |
+
|
| 66 |
+
uncertainty: float | None = Field(default=None, description="Incertitude %")
|
| 67 |
+
source: str | None = Field(default=None, description="Source")
|
| 68 |
+
status: str | None = Field(default=None, description="Statut")
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class EnergyFactor(BaseModel):
|
| 72 |
+
"""Facteur d'émission énergie."""
|
| 73 |
+
|
| 74 |
+
id: str
|
| 75 |
+
energy_type: str = Field(description="Type d'énergie")
|
| 76 |
+
specification: str | None = Field(default=None, description="Spécification")
|
| 77 |
+
|
| 78 |
+
value: float = Field(description="Valeur en kgCO2e")
|
| 79 |
+
unit: str = Field(description="Unité (kgCO2e/kWh, kgCO2e/kg, etc.)")
|
| 80 |
+
|
| 81 |
+
# Décomposition par gaz
|
| 82 |
+
co2_fossil: float | None = Field(default=None, description="CO2 fossile")
|
| 83 |
+
co2_biogenic: float | None = Field(default=None, description="CO2 biogénique")
|
| 84 |
+
ch4: float | None = Field(default=None, description="Méthane")
|
| 85 |
+
n2o: float | None = Field(default=None, description="Protoxyde d'azote")
|
| 86 |
+
|
| 87 |
+
uncertainty: float | None = Field(default=None, description="Incertitude %")
|
| 88 |
+
source: str | None = Field(default=None, description="Source")
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class WasteFactor(BaseModel):
|
| 92 |
+
"""Facteur d'émission déchets."""
|
| 93 |
+
|
| 94 |
+
id: str
|
| 95 |
+
waste_type: str = Field(description="Type de déchet")
|
| 96 |
+
treatment: str = Field(description="Traitement (compost, incinération, etc.)")
|
| 97 |
+
|
| 98 |
+
value: float = Field(description="Valeur en kgCO2e/tonne")
|
| 99 |
+
unit: str = Field(default="kgCO2e/tonne", description="Unité")
|
| 100 |
+
|
| 101 |
+
# Décomposition par gaz
|
| 102 |
+
co2_fossil: float | None = None
|
| 103 |
+
co2_biogenic: float | None = None
|
| 104 |
+
ch4: float | None = None
|
| 105 |
+
n2o: float | None = None
|
| 106 |
+
|
| 107 |
+
uncertainty: float | None = None
|
| 108 |
+
source: str | None = None
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class ComparisonResult(BaseModel):
|
| 112 |
+
"""Résultat de comparaison entre 2 facteurs d'émission."""
|
| 113 |
+
|
| 114 |
+
factor1: EmissionFactor
|
| 115 |
+
factor2: EmissionFactor
|
| 116 |
+
|
| 117 |
+
absolute_difference: float = Field(description="Différence absolue (FE2 - FE1)")
|
| 118 |
+
relative_difference: float = Field(description="Différence relative en %")
|
| 119 |
+
|
| 120 |
+
# Analyse
|
| 121 |
+
lower_bound_diff: float | None = Field(
|
| 122 |
+
default=None,
|
| 123 |
+
description="Différence min compte tenu des incertitudes"
|
| 124 |
+
)
|
| 125 |
+
upper_bound_diff: float | None = Field(
|
| 126 |
+
default=None,
|
| 127 |
+
description="Différence max compte tenu des incertitudes"
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
recommendation: str | None = Field(
|
| 131 |
+
default=None,
|
| 132 |
+
description="Recommandation d'usage"
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
class SearchResult(BaseModel):
|
| 137 |
+
"""Résultat de recherche unifiée."""
|
| 138 |
+
|
| 139 |
+
total: int = Field(description="Nombre total de résultats")
|
| 140 |
+
results: list[EmissionFactor] = Field(description="Facteurs d'émission trouvés")
|
| 141 |
+
sources: dict[str, int] = Field(
|
| 142 |
+
description="Nombre de résultats par source"
|
| 143 |
+
)
|
src/tools.py
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Outils MCP pour l'API ADEME (AGRIBALYSE + Base Carbone)."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
from typing import Any
|
| 5 |
+
|
| 6 |
+
from .clients import AgribalyseClient, BaseCarboneClient
|
| 7 |
+
|
| 8 |
+
# Clients globaux (initialisés au premier appel)
|
| 9 |
+
_agribalyse_client: AgribalyseClient | None = None
|
| 10 |
+
_base_carbone_client: BaseCarboneClient | None = None
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def _get_agribalyse() -> AgribalyseClient:
|
| 14 |
+
global _agribalyse_client
|
| 15 |
+
if _agribalyse_client is None:
|
| 16 |
+
_agribalyse_client = AgribalyseClient()
|
| 17 |
+
return _agribalyse_client
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _get_base_carbone() -> BaseCarboneClient:
|
| 21 |
+
global _base_carbone_client
|
| 22 |
+
if _base_carbone_client is None:
|
| 23 |
+
_base_carbone_client = BaseCarboneClient()
|
| 24 |
+
return _base_carbone_client
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _format_agribalyse_result(item: dict[str, Any]) -> dict[str, Any]:
|
| 28 |
+
"""Formate un résultat AGRIBALYSE en format unifié."""
|
| 29 |
+
return {
|
| 30 |
+
"source": "agribalyse",
|
| 31 |
+
"id": str(item.get("Code_CIQUAL", "")),
|
| 32 |
+
"name": item.get("Nom_du_Produit_en_Français", ""),
|
| 33 |
+
"name_en": item.get("LCI_Name", ""),
|
| 34 |
+
"value": item.get("Changement_climatique", 0),
|
| 35 |
+
"unit": "kgCO2e/kg",
|
| 36 |
+
"category": item.get("Groupe_d'aliment", ""),
|
| 37 |
+
"subcategory": item.get("Sous-groupe_d'aliment", ""),
|
| 38 |
+
"dqr": item.get("DQR"),
|
| 39 |
+
"ef_score": item.get("Score_unique_EF"),
|
| 40 |
+
"climate_fossil": item.get("Changement_climatique_-_émissions_fossiles"),
|
| 41 |
+
"climate_biogenic": item.get("Changement_climatique_-_émissions_biogéniques"),
|
| 42 |
+
"climate_land_use": item.get("Changement_climatique_-_émissions_liées_au_changement_d'affectation_des_sols"),
|
| 43 |
+
"delivery": item.get("Livraison"),
|
| 44 |
+
"preparation": item.get("Préparation"),
|
| 45 |
+
"packaging": item.get("Approche_emballage_"),
|
| 46 |
+
"by_plane": item.get("code_avion", False),
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _format_base_carbone_result(item: dict[str, Any]) -> dict[str, Any]:
|
| 51 |
+
"""Formate un résultat Base Carbone en format unifié."""
|
| 52 |
+
# Construire le nom complet
|
| 53 |
+
name_parts = [item.get("Nom_base_français", "")]
|
| 54 |
+
if item.get("Nom_attribut_français"):
|
| 55 |
+
name_parts.append(item.get("Nom_attribut_français"))
|
| 56 |
+
if item.get("Nom_frontière_français"):
|
| 57 |
+
name_parts.append(f"({item.get('Nom_frontière_français')})")
|
| 58 |
+
|
| 59 |
+
return {
|
| 60 |
+
"source": "base_carbone",
|
| 61 |
+
"id": item.get("Identifiant_de_l'élément", ""),
|
| 62 |
+
"name": " - ".join(filter(None, name_parts)),
|
| 63 |
+
"name_en": item.get("Nom_base_anglais", ""),
|
| 64 |
+
"value": item.get("Total_poste_non_décomposé", 0),
|
| 65 |
+
"unit": item.get("Unité_français", ""),
|
| 66 |
+
"category": item.get("Code_de_la_catégorie", ""),
|
| 67 |
+
"uncertainty": item.get("Incertitude"),
|
| 68 |
+
"dqr": item.get("Qualité"),
|
| 69 |
+
"status": item.get("Statut_de_l'élément"),
|
| 70 |
+
"validity": item.get("Période_de_validité"),
|
| 71 |
+
"methodology": {
|
| 72 |
+
"source": item.get("Source"),
|
| 73 |
+
"program": item.get("Programme"),
|
| 74 |
+
"url": item.get("Url_du_programme"),
|
| 75 |
+
"contributor": item.get("Contributeur"),
|
| 76 |
+
"structure": item.get("Structure"),
|
| 77 |
+
"comment": item.get("Commentaire_français"),
|
| 78 |
+
"regulations": item.get("Réglementations"),
|
| 79 |
+
},
|
| 80 |
+
"decomposition_gas": {
|
| 81 |
+
"co2_fossil": item.get("CO2f"),
|
| 82 |
+
"co2_biogenic": item.get("CO2b"),
|
| 83 |
+
"ch4_fossil": item.get("CH4f"),
|
| 84 |
+
"ch4_biogenic": item.get("CH4b"),
|
| 85 |
+
"n2o": item.get("N2O"),
|
| 86 |
+
"other_ghg": item.get("Autres_GES"),
|
| 87 |
+
},
|
| 88 |
+
"quality_indicators": {
|
| 89 |
+
"M": item.get("Qualité_M"),
|
| 90 |
+
"TeR": item.get("Qualité_TeR"),
|
| 91 |
+
"C": item.get("Qualité_C"),
|
| 92 |
+
"TiR": item.get("Qualité_TiR"),
|
| 93 |
+
"GR": item.get("Qualité_GR"),
|
| 94 |
+
"P": item.get("Qualité_P"),
|
| 95 |
+
"transparency": item.get("Transparence"),
|
| 96 |
+
},
|
| 97 |
+
"location": item.get("Localisation_géographique"),
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
# ============================================================================
|
| 102 |
+
# OUTILS MCP
|
| 103 |
+
# ============================================================================
|
| 104 |
+
|
| 105 |
+
async def search_emission_factors(
|
| 106 |
+
query: str,
|
| 107 |
+
source: str = "all",
|
| 108 |
+
size: int = 10
|
| 109 |
+
) -> str:
|
| 110 |
+
"""Recherche unifiée de facteurs d'émission dans AGRIBALYSE et Base Carbone.
|
| 111 |
+
|
| 112 |
+
Permet de trouver des facteurs d'émission (FE) pour calculer l'impact carbone
|
| 113 |
+
de différentes activités : alimentation, transport, énergie, déchets, équipements.
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
query: Terme de recherche (ex: "boeuf", "camion diesel", "gaz naturel").
|
| 117 |
+
source: Source à interroger - "all" (les deux), "agribalyse" (alimentation),
|
| 118 |
+
ou "base_carbone" (transport, énergie, déchets, équipements).
|
| 119 |
+
size: Nombre de résultats par source (défaut: 10, max: 50).
|
| 120 |
+
|
| 121 |
+
Returns:
|
| 122 |
+
JSON avec les résultats unifiés des deux bases, incluant :
|
| 123 |
+
- id, name, value (kgCO2e), unit, category
|
| 124 |
+
- source d'origine (agribalyse/base_carbone)
|
| 125 |
+
- métadonnées de qualité (DQR, incertitude)
|
| 126 |
+
|
| 127 |
+
Example:
|
| 128 |
+
search_emission_factors("poulet", source="agribalyse")
|
| 129 |
+
search_emission_factors("camion", source="base_carbone")
|
| 130 |
+
search_emission_factors("électricité")
|
| 131 |
+
"""
|
| 132 |
+
results = []
|
| 133 |
+
sources_count = {"agribalyse": 0, "base_carbone": 0}
|
| 134 |
+
size = min(size, 50)
|
| 135 |
+
|
| 136 |
+
try:
|
| 137 |
+
# Recherche AGRIBALYSE
|
| 138 |
+
if source in ["all", "agribalyse"]:
|
| 139 |
+
client = _get_agribalyse()
|
| 140 |
+
agri_data = await client.search(query, size=size)
|
| 141 |
+
for item in agri_data.get("results", []):
|
| 142 |
+
results.append(_format_agribalyse_result(item))
|
| 143 |
+
sources_count["agribalyse"] += 1
|
| 144 |
+
|
| 145 |
+
# Recherche Base Carbone
|
| 146 |
+
if source in ["all", "base_carbone"]:
|
| 147 |
+
client = _get_base_carbone()
|
| 148 |
+
bc_data = await client.search(query, size=size)
|
| 149 |
+
for item in bc_data.get("results", []):
|
| 150 |
+
if item.get("Type_Ligne") == "Elément":
|
| 151 |
+
results.append(_format_base_carbone_result(item))
|
| 152 |
+
sources_count["base_carbone"] += 1
|
| 153 |
+
|
| 154 |
+
return json.dumps({
|
| 155 |
+
"total": len(results),
|
| 156 |
+
"sources": sources_count,
|
| 157 |
+
"results": results
|
| 158 |
+
}, ensure_ascii=False, indent=2)
|
| 159 |
+
|
| 160 |
+
except Exception as e:
|
| 161 |
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
async def get_emission_factor_details(
|
| 165 |
+
identifier: str,
|
| 166 |
+
source: str = "base_carbone"
|
| 167 |
+
) -> str:
|
| 168 |
+
"""Obtient les détails méthodologiques complets d'un facteur d'émission.
|
| 169 |
+
|
| 170 |
+
Retourne toutes les informations disponibles sur un FE : valeur, incertitude,
|
| 171 |
+
source méthodologique, décomposition par gaz, indicateurs de qualité, etc.
|
| 172 |
+
Essentiel pour comprendre comment le FE a été calculé et sa fiabilité.
|
| 173 |
+
|
| 174 |
+
Args:
|
| 175 |
+
identifier: Identifiant du FE (Code_CIQUAL pour AGRIBALYSE,
|
| 176 |
+
Identifiant_de_l'élément pour Base Carbone).
|
| 177 |
+
source: "agribalyse" ou "base_carbone".
|
| 178 |
+
|
| 179 |
+
Returns:
|
| 180 |
+
JSON avec tous les détails méthodologiques :
|
| 181 |
+
- Valeur et unité
|
| 182 |
+
- Incertitude et indicateurs de qualité (DQR, M, TeR, C, TiR, GR, P)
|
| 183 |
+
- Source, programme, contributeur
|
| 184 |
+
- Décomposition par gaz (CO2f, CO2b, CH4, N2O)
|
| 185 |
+
- Commentaires méthodologiques
|
| 186 |
+
- Période de validité et statut
|
| 187 |
+
|
| 188 |
+
Example:
|
| 189 |
+
get_emission_factor_details("27002", source="base_carbone")
|
| 190 |
+
get_emission_factor_details("13050", source="agribalyse")
|
| 191 |
+
"""
|
| 192 |
+
try:
|
| 193 |
+
if source == "agribalyse":
|
| 194 |
+
client = _get_agribalyse()
|
| 195 |
+
item = await client.get_by_code(int(identifier))
|
| 196 |
+
if item:
|
| 197 |
+
result = _format_agribalyse_result(item)
|
| 198 |
+
# Ajouter tous les impacts environnementaux
|
| 199 |
+
result["all_impacts"] = {
|
| 200 |
+
"climate_change": item.get("Changement_climatique"),
|
| 201 |
+
"acidification": item.get("Acidification_terrestre_et_eaux_douces"),
|
| 202 |
+
"eutrophication_marine": item.get("Eutrophisation_marine"),
|
| 203 |
+
"eutrophication_freshwater": item.get("Eutrophisation_eaux_douces"),
|
| 204 |
+
"eutrophication_terrestrial": item.get("Eutrophisation_terrestre"),
|
| 205 |
+
"land_use": item.get("Utilisation_du_sol"),
|
| 206 |
+
"water_depletion": item.get("Épuisement_des_ressources_eau"),
|
| 207 |
+
"energy_depletion": item.get("Épuisement_des_ressources_énergétiques"),
|
| 208 |
+
"mineral_depletion": item.get("Épuisement_des_ressources_minéraux"),
|
| 209 |
+
"particulate_matter": item.get("Particules_fines"),
|
| 210 |
+
"ozone_formation": item.get("Formation_photochimique_d'ozone"),
|
| 211 |
+
"ozone_depletion": item.get("Appauvrissement_de_la_couche_d'ozone"),
|
| 212 |
+
"ionizing_radiation": item.get("Rayonnements_ionisants"),
|
| 213 |
+
"ecotoxicity": item.get("Écotoxicité_pour_écosystèmes_aquatiques_d'eau_douce"),
|
| 214 |
+
"toxicity_cancer": item.get("Effets_toxicologiques_sur_la_santé_humaine___substances_cancérogènes"),
|
| 215 |
+
"toxicity_non_cancer": item.get("Effets_toxicologiques_sur_la_santé_humaine___substances_non-cancérogènes"),
|
| 216 |
+
}
|
| 217 |
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
| 218 |
+
return json.dumps({"error": f"Aliment {identifier} non trouvé"})
|
| 219 |
+
|
| 220 |
+
else: # base_carbone
|
| 221 |
+
client = _get_base_carbone()
|
| 222 |
+
items = await client.get_by_id(identifier)
|
| 223 |
+
if items:
|
| 224 |
+
# Récupérer l'élément principal et ses postes
|
| 225 |
+
main_element = None
|
| 226 |
+
posts = []
|
| 227 |
+
for item in items:
|
| 228 |
+
if item.get("Type_Ligne") == "Elément":
|
| 229 |
+
main_element = _format_base_carbone_result(item)
|
| 230 |
+
elif item.get("Type_Ligne") == "Poste":
|
| 231 |
+
posts.append({
|
| 232 |
+
"post_name": item.get("Nom_poste_français"),
|
| 233 |
+
"post_type": item.get("Type_poste"),
|
| 234 |
+
"value": item.get("Total_poste_non_décomposé"),
|
| 235 |
+
})
|
| 236 |
+
|
| 237 |
+
if main_element:
|
| 238 |
+
main_element["decomposition_posts"] = posts
|
| 239 |
+
return json.dumps(main_element, ensure_ascii=False, indent=2)
|
| 240 |
+
|
| 241 |
+
return json.dumps({"error": f"FE {identifier} non trouvé"})
|
| 242 |
+
|
| 243 |
+
except Exception as e:
|
| 244 |
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
async def compare_emission_factors(
|
| 248 |
+
id1: str,
|
| 249 |
+
id2: str,
|
| 250 |
+
source1: str = "base_carbone",
|
| 251 |
+
source2: str = "base_carbone"
|
| 252 |
+
) -> str:
|
| 253 |
+
"""Compare deux facteurs d'émission avec analyse méthodologique.
|
| 254 |
+
|
| 255 |
+
Permet de comparer deux FE pour une analyse de sensibilité : écart de valeur,
|
| 256 |
+
comparaison des incertitudes, différences de périmètre méthodologique.
|
| 257 |
+
Utile pour choisir entre deux options (ex: diesel vs électrique).
|
| 258 |
+
|
| 259 |
+
Args:
|
| 260 |
+
id1: Identifiant du premier FE.
|
| 261 |
+
id2: Identifiant du second FE.
|
| 262 |
+
source1: Source du premier FE ("agribalyse" ou "base_carbone").
|
| 263 |
+
source2: Source du second FE ("agribalyse" ou "base_carbone").
|
| 264 |
+
|
| 265 |
+
Returns:
|
| 266 |
+
JSON avec la comparaison détaillée :
|
| 267 |
+
- Valeurs des deux FE
|
| 268 |
+
- Différence absolue et relative (%)
|
| 269 |
+
- Plage de différence compte tenu des incertitudes
|
| 270 |
+
- Comparaison des qualités de données
|
| 271 |
+
- Recommandation d'usage
|
| 272 |
+
|
| 273 |
+
Example:
|
| 274 |
+
compare_emission_factors("28022", "28024") # VUL diesel vs électrique
|
| 275 |
+
compare_emission_factors("13050", "13029", "agribalyse", "agribalyse") # Boeuf vs poulet
|
| 276 |
+
"""
|
| 277 |
+
try:
|
| 278 |
+
# Récupérer les détails des deux FE
|
| 279 |
+
details1_json = await get_emission_factor_details(id1, source1)
|
| 280 |
+
details2_json = await get_emission_factor_details(id2, source2)
|
| 281 |
+
|
| 282 |
+
details1 = json.loads(details1_json)
|
| 283 |
+
details2 = json.loads(details2_json)
|
| 284 |
+
|
| 285 |
+
if "error" in details1:
|
| 286 |
+
return json.dumps({"error": f"FE1: {details1['error']}"})
|
| 287 |
+
if "error" in details2:
|
| 288 |
+
return json.dumps({"error": f"FE2: {details2['error']}"})
|
| 289 |
+
|
| 290 |
+
# Calculs de comparaison
|
| 291 |
+
val1 = details1.get("value", 0)
|
| 292 |
+
val2 = details2.get("value", 0)
|
| 293 |
+
|
| 294 |
+
abs_diff = val2 - val1
|
| 295 |
+
rel_diff = ((val2 - val1) / val1 * 100) if val1 != 0 else None
|
| 296 |
+
|
| 297 |
+
# Calcul avec incertitudes
|
| 298 |
+
unc1 = details1.get("uncertainty", 0) or 0
|
| 299 |
+
unc2 = details2.get("uncertainty", 0) or 0
|
| 300 |
+
|
| 301 |
+
lower_bound = None
|
| 302 |
+
upper_bound = None
|
| 303 |
+
if unc1 > 0 or unc2 > 0:
|
| 304 |
+
val1_min = val1 * (1 - unc1/100)
|
| 305 |
+
val1_max = val1 * (1 + unc1/100)
|
| 306 |
+
val2_min = val2 * (1 - unc2/100)
|
| 307 |
+
val2_max = val2 * (1 + unc2/100)
|
| 308 |
+
|
| 309 |
+
if val1 > 0:
|
| 310 |
+
lower_bound = (val2_min - val1_max) / val1 * 100
|
| 311 |
+
upper_bound = (val2_max - val1_min) / val1 * 100
|
| 312 |
+
|
| 313 |
+
# Recommandation
|
| 314 |
+
recommendation = None
|
| 315 |
+
if rel_diff is not None:
|
| 316 |
+
if abs(rel_diff) < 10:
|
| 317 |
+
recommendation = "Écart faible (<10%), les deux options sont équivalentes."
|
| 318 |
+
elif rel_diff < -20:
|
| 319 |
+
recommendation = f"Option 2 ({details2['name']}) significativement meilleure (-{abs(rel_diff):.0f}%)."
|
| 320 |
+
elif rel_diff > 20:
|
| 321 |
+
recommendation = f"Option 1 ({details1['name']}) significativement meilleure (+{rel_diff:.0f}%)."
|
| 322 |
+
else:
|
| 323 |
+
recommendation = "Écart modéré, considérer d'autres critères (coût, faisabilité)."
|
| 324 |
+
|
| 325 |
+
return json.dumps({
|
| 326 |
+
"factor1": {
|
| 327 |
+
"id": id1,
|
| 328 |
+
"source": source1,
|
| 329 |
+
"name": details1.get("name"),
|
| 330 |
+
"value": val1,
|
| 331 |
+
"unit": details1.get("unit"),
|
| 332 |
+
"uncertainty": unc1,
|
| 333 |
+
"dqr": details1.get("dqr"),
|
| 334 |
+
},
|
| 335 |
+
"factor2": {
|
| 336 |
+
"id": id2,
|
| 337 |
+
"source": source2,
|
| 338 |
+
"name": details2.get("name"),
|
| 339 |
+
"value": val2,
|
| 340 |
+
"unit": details2.get("unit"),
|
| 341 |
+
"uncertainty": unc2,
|
| 342 |
+
"dqr": details2.get("dqr"),
|
| 343 |
+
},
|
| 344 |
+
"comparison": {
|
| 345 |
+
"absolute_difference": round(abs_diff, 4),
|
| 346 |
+
"relative_difference_percent": round(rel_diff, 1) if rel_diff else None,
|
| 347 |
+
"range_with_uncertainty": {
|
| 348 |
+
"lower_bound_percent": round(lower_bound, 1) if lower_bound else None,
|
| 349 |
+
"upper_bound_percent": round(upper_bound, 1) if upper_bound else None,
|
| 350 |
+
},
|
| 351 |
+
"recommendation": recommendation,
|
| 352 |
+
}
|
| 353 |
+
}, ensure_ascii=False, indent=2)
|
| 354 |
+
|
| 355 |
+
except Exception as e:
|
| 356 |
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
async def search_food(
|
| 360 |
+
query: str,
|
| 361 |
+
group: str = "",
|
| 362 |
+
size: int = 10
|
| 363 |
+
) -> str:
|
| 364 |
+
"""Recherche d'aliments dans la base AGRIBALYSE.
|
| 365 |
+
|
| 366 |
+
AGRIBALYSE contient les impacts environnementaux de 2500+ produits alimentaires
|
| 367 |
+
français, calculés sur tout le cycle de vie (du champ à l'assiette).
|
| 368 |
+
|
| 369 |
+
Args:
|
| 370 |
+
query: Terme de recherche (ex: "steak", "yaourt", "pain").
|
| 371 |
+
group: Filtrer par groupe d'aliment (ex: "viandes", "produits laitiers").
|
| 372 |
+
Laisser vide pour chercher dans tous les groupes.
|
| 373 |
+
size: Nombre de résultats (défaut: 10, max: 50).
|
| 374 |
+
|
| 375 |
+
Returns:
|
| 376 |
+
JSON avec les aliments trouvés :
|
| 377 |
+
- Nom, code CIQUAL, groupe/sous-groupe
|
| 378 |
+
- Impact climatique (kgCO2e/kg)
|
| 379 |
+
- Décomposition (fossile, biogénique, changement d'affectation)
|
| 380 |
+
- Score environnemental global (EF Score)
|
| 381 |
+
- Qualité des données (DQR)
|
| 382 |
+
|
| 383 |
+
Example:
|
| 384 |
+
search_food("boeuf")
|
| 385 |
+
search_food("fromage", group="produits laitiers")
|
| 386 |
+
"""
|
| 387 |
+
try:
|
| 388 |
+
client = _get_agribalyse()
|
| 389 |
+
data = await client.search(query, size=min(size, 50))
|
| 390 |
+
|
| 391 |
+
results = []
|
| 392 |
+
for item in data.get("results", []):
|
| 393 |
+
# Filtrer par groupe si spécifié
|
| 394 |
+
if group and group.lower() not in item.get("Groupe_d'aliment", "").lower():
|
| 395 |
+
continue
|
| 396 |
+
results.append(_format_agribalyse_result(item))
|
| 397 |
+
|
| 398 |
+
return json.dumps({
|
| 399 |
+
"total": len(results),
|
| 400 |
+
"results": results
|
| 401 |
+
}, ensure_ascii=False, indent=2)
|
| 402 |
+
|
| 403 |
+
except Exception as e:
|
| 404 |
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
async def get_transport_factors(
|
| 408 |
+
vehicle_type: str = "",
|
| 409 |
+
fuel_type: str = "",
|
| 410 |
+
size: int = 20
|
| 411 |
+
) -> str:
|
| 412 |
+
"""Recherche de facteurs d'émission pour le transport.
|
| 413 |
+
|
| 414 |
+
Permet de trouver les FE de différents modes de transport : routier (voiture,
|
| 415 |
+
VUL, poids lourd), ferroviaire, maritime, aérien. Inclut différentes motorisations
|
| 416 |
+
(diesel, essence, électrique, hybride, GNV).
|
| 417 |
+
|
| 418 |
+
Args:
|
| 419 |
+
vehicle_type: Type de véhicule (ex: "voiture", "VUL", "poids lourd", "train").
|
| 420 |
+
fuel_type: Type de carburant (ex: "diesel", "essence", "électrique", "hybride").
|
| 421 |
+
size: Nombre de résultats (défaut: 20).
|
| 422 |
+
|
| 423 |
+
Returns:
|
| 424 |
+
JSON avec les FE transport :
|
| 425 |
+
- Nom du véhicule et motorisation
|
| 426 |
+
- Valeur (kgCO2e/km ou kgCO2e/t.km)
|
| 427 |
+
- Incertitude
|
| 428 |
+
- Source méthodologique
|
| 429 |
+
|
| 430 |
+
Example:
|
| 431 |
+
get_transport_factors("VUL", "diesel")
|
| 432 |
+
get_transport_factors("poids lourd")
|
| 433 |
+
get_transport_factors(fuel_type="électrique")
|
| 434 |
+
"""
|
| 435 |
+
try:
|
| 436 |
+
client = _get_base_carbone()
|
| 437 |
+
data = await client.search_transport(vehicle_type or None, fuel_type or None, size)
|
| 438 |
+
|
| 439 |
+
results = []
|
| 440 |
+
for item in data.get("results", []):
|
| 441 |
+
if item.get("Type_Ligne") == "Elément":
|
| 442 |
+
results.append(_format_base_carbone_result(item))
|
| 443 |
+
|
| 444 |
+
return json.dumps({
|
| 445 |
+
"total": len(results),
|
| 446 |
+
"results": results
|
| 447 |
+
}, ensure_ascii=False, indent=2)
|
| 448 |
+
|
| 449 |
+
except Exception as e:
|
| 450 |
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
async def get_energy_factors(
|
| 454 |
+
energy_type: str = "",
|
| 455 |
+
size: int = 20
|
| 456 |
+
) -> str:
|
| 457 |
+
"""Recherche de facteurs d'émission pour l'énergie.
|
| 458 |
+
|
| 459 |
+
Permet de trouver les FE des différentes sources d'énergie : gaz naturel,
|
| 460 |
+
électricité, fioul, charbon, bois, etc. Utile pour calculer l'impact
|
| 461 |
+
du chauffage, de la cuisson, ou de la consommation électrique.
|
| 462 |
+
|
| 463 |
+
Args:
|
| 464 |
+
energy_type: Type d'énergie (ex: "gaz naturel", "électricité", "fioul").
|
| 465 |
+
size: Nombre de résultats (défaut: 20).
|
| 466 |
+
|
| 467 |
+
Returns:
|
| 468 |
+
JSON avec les FE énergie :
|
| 469 |
+
- Type d'énergie
|
| 470 |
+
- Valeur (kgCO2e/kWh ou kgCO2e/kg)
|
| 471 |
+
- Décomposition par gaz (CO2, CH4, N2O)
|
| 472 |
+
- Incertitude
|
| 473 |
+
|
| 474 |
+
Example:
|
| 475 |
+
get_energy_factors("gaz naturel")
|
| 476 |
+
get_energy_factors("électricité")
|
| 477 |
+
"""
|
| 478 |
+
try:
|
| 479 |
+
client = _get_base_carbone()
|
| 480 |
+
data = await client.search_energy(energy_type or None, size)
|
| 481 |
+
|
| 482 |
+
results = []
|
| 483 |
+
for item in data.get("results", []):
|
| 484 |
+
if item.get("Type_Ligne") == "Elément":
|
| 485 |
+
results.append(_format_base_carbone_result(item))
|
| 486 |
+
|
| 487 |
+
return json.dumps({
|
| 488 |
+
"total": len(results),
|
| 489 |
+
"results": results
|
| 490 |
+
}, ensure_ascii=False, indent=2)
|
| 491 |
+
|
| 492 |
+
except Exception as e:
|
| 493 |
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
async def get_waste_factors(
|
| 497 |
+
waste_type: str = "",
|
| 498 |
+
treatment: str = "",
|
| 499 |
+
size: int = 20
|
| 500 |
+
) -> str:
|
| 501 |
+
"""Recherche de facteurs d'émission pour les déchets.
|
| 502 |
+
|
| 503 |
+
Permet de trouver les FE des différents types de déchets selon leur
|
| 504 |
+
traitement : compostage, incinération, stockage (décharge), méthanisation.
|
| 505 |
+
|
| 506 |
+
Args:
|
| 507 |
+
waste_type: Type de déchet (ex: "alimentaire", "plastique", "papier").
|
| 508 |
+
treatment: Mode de traitement (ex: "compost", "incinération", "stockage").
|
| 509 |
+
size: Nombre de résultats (défaut: 20).
|
| 510 |
+
|
| 511 |
+
Returns:
|
| 512 |
+
JSON avec les FE déchets :
|
| 513 |
+
- Type de déchet et traitement
|
| 514 |
+
- Valeur (kgCO2e/tonne)
|
| 515 |
+
- Décomposition par gaz
|
| 516 |
+
- Incertitude
|
| 517 |
+
|
| 518 |
+
Example:
|
| 519 |
+
get_waste_factors("alimentaire", "compost")
|
| 520 |
+
get_waste_factors("plastique", "incinération")
|
| 521 |
+
"""
|
| 522 |
+
try:
|
| 523 |
+
client = _get_base_carbone()
|
| 524 |
+
data = await client.search_waste(waste_type or None, treatment or None, size)
|
| 525 |
+
|
| 526 |
+
results = []
|
| 527 |
+
for item in data.get("results", []):
|
| 528 |
+
if item.get("Type_Ligne") == "Elément":
|
| 529 |
+
results.append(_format_base_carbone_result(item))
|
| 530 |
+
|
| 531 |
+
return json.dumps({
|
| 532 |
+
"total": len(results),
|
| 533 |
+
"results": results
|
| 534 |
+
}, ensure_ascii=False, indent=2)
|
| 535 |
+
|
| 536 |
+
except Exception as e:
|
| 537 |
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 538 |
+
|
| 539 |
+
|
| 540 |
+
async def get_equipment_factors(
|
| 541 |
+
equipment_type: str = "",
|
| 542 |
+
size: int = 20
|
| 543 |
+
) -> str:
|
| 544 |
+
"""Recherche de facteurs d'émission pour les équipements.
|
| 545 |
+
|
| 546 |
+
Permet de trouver les FE des équipements informatiques et électroménagers :
|
| 547 |
+
ordinateurs, téléphones, télévisions, réfrigérateurs, etc.
|
| 548 |
+
Inclut la décomposition par poste (matières premières, fabrication, transport).
|
| 549 |
+
|
| 550 |
+
Args:
|
| 551 |
+
equipment_type: Type d'équipement (ex: "ordinateur", "téléphone", "télévision").
|
| 552 |
+
size: Nombre de résultats (défaut: 20).
|
| 553 |
+
|
| 554 |
+
Returns:
|
| 555 |
+
JSON avec les FE équipements :
|
| 556 |
+
- Type d'équipement
|
| 557 |
+
- Valeur (kgCO2e/unité)
|
| 558 |
+
- Décomposition par poste (si disponible)
|
| 559 |
+
- Incertitude
|
| 560 |
+
|
| 561 |
+
Example:
|
| 562 |
+
get_equipment_factors("ordinateur portable")
|
| 563 |
+
get_equipment_factors("smartphone")
|
| 564 |
+
"""
|
| 565 |
+
try:
|
| 566 |
+
client = _get_base_carbone()
|
| 567 |
+
data = await client.search_equipment(equipment_type or None, size)
|
| 568 |
+
|
| 569 |
+
results = []
|
| 570 |
+
for item in data.get("results", []):
|
| 571 |
+
if item.get("Type_Ligne") == "Elément":
|
| 572 |
+
results.append(_format_base_carbone_result(item))
|
| 573 |
+
|
| 574 |
+
return json.dumps({
|
| 575 |
+
"total": len(results),
|
| 576 |
+
"results": results
|
| 577 |
+
}, ensure_ascii=False, indent=2)
|
| 578 |
+
|
| 579 |
+
except Exception as e:
|
| 580 |
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
async def get_categories(
|
| 584 |
+
source: str = "all"
|
| 585 |
+
) -> str:
|
| 586 |
+
"""Liste les catégories disponibles dans les bases de données.
|
| 587 |
+
|
| 588 |
+
Permet de découvrir les catégories de facteurs d'émission disponibles
|
| 589 |
+
pour orienter les recherches.
|
| 590 |
+
|
| 591 |
+
Args:
|
| 592 |
+
source: "agribalyse" (groupes d'aliments), "base_carbone" (catégories FE),
|
| 593 |
+
ou "all" (les deux).
|
| 594 |
+
|
| 595 |
+
Returns:
|
| 596 |
+
JSON avec les catégories par source.
|
| 597 |
+
|
| 598 |
+
Example:
|
| 599 |
+
get_categories("agribalyse")
|
| 600 |
+
get_categories("base_carbone")
|
| 601 |
+
"""
|
| 602 |
+
try:
|
| 603 |
+
result = {}
|
| 604 |
+
|
| 605 |
+
if source in ["all", "agribalyse"]:
|
| 606 |
+
client = _get_agribalyse()
|
| 607 |
+
categories = await client.get_categories()
|
| 608 |
+
result["agribalyse"] = {
|
| 609 |
+
"description": "Groupes d'aliments AGRIBALYSE",
|
| 610 |
+
"categories": categories
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
if source in ["all", "base_carbone"]:
|
| 614 |
+
client = _get_base_carbone()
|
| 615 |
+
categories = await client.get_categories()
|
| 616 |
+
# Extraire les catégories racines
|
| 617 |
+
root_categories = set()
|
| 618 |
+
for cat in categories:
|
| 619 |
+
root = cat["category"].split(" > ")[0]
|
| 620 |
+
root_categories.add(root)
|
| 621 |
+
|
| 622 |
+
result["base_carbone"] = {
|
| 623 |
+
"description": "Catégories principales Base Carbone",
|
| 624 |
+
"categories": sorted(list(root_categories))
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
| 628 |
+
|
| 629 |
+
except Exception as e:
|
| 630 |
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
# Liste des outils pour export
|
| 634 |
+
ALL_TOOLS = [
|
| 635 |
+
search_emission_factors,
|
| 636 |
+
get_emission_factor_details,
|
| 637 |
+
compare_emission_factors,
|
| 638 |
+
search_food,
|
| 639 |
+
get_transport_factors,
|
| 640 |
+
get_energy_factors,
|
| 641 |
+
get_waste_factors,
|
| 642 |
+
get_equipment_factors,
|
| 643 |
+
get_categories,
|
| 644 |
+
]
|