From ed288823c98be29201ff0121068474d1b8d35932 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Thu, 9 Jul 2026 20:35:24 +0200 Subject: [PATCH] =?UTF-8?q?feat(mcp):=20expose=20les=204=20fonctions=20m?= =?UTF-8?q?=C3=A9tier=20via=20@mcp=5Fenabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- src/mcp/tools.py | 86 +++++++++++++++++++++++++++++++++++++++++ tests/mcp/test_tools.py | 33 ++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 src/mcp/tools.py create mode 100644 tests/mcp/test_tools.py diff --git a/src/mcp/tools.py b/src/mcp/tools.py new file mode 100644 index 0000000..1f08a3b --- /dev/null +++ b/src/mcp/tools.py @@ -0,0 +1,86 @@ +from dash.mcp import mcp_enabled + +from src.mcp import queries +from src.utils.tracking import track_mcp_tool + + +@mcp_enabled(name="rechercher_organisations", expose_docstring=True) +def rechercher_organisations( + query: str, + type: str = "acheteur", + limite: int = 20, # noqa: A002 +) -> list[dict]: + """Recherche des acheteurs ou titulaires publics par nom. + + Utiliser en premier pour résoudre un nom d'organisation vers son + identifiant, à passer ensuite à stats_acheteur / stats_titulaire. + + query: texte libre (nom d'organisation). + type: "acheteur" ou "titulaire". + Retourne une liste de {id, nom, departement}. + """ + track_mcp_tool("rechercher_organisations", query=query) + return queries.search_organisations(query, type, limite) + + +@mcp_enabled(name="stats_acheteur", expose_docstring=True) +def stats_acheteur(acheteur_id: str) -> dict: + """Statistiques agrégées d'un acheteur public (par identifiant). + + Retourne nombre de marchés, montant total, répartition annuelle, + principaux titulaires et principaux codes CPV. + """ + track_mcp_tool("stats_acheteur") + return queries.compute_org_stats("acheteur", acheteur_id) + + +@mcp_enabled(name="stats_titulaire", expose_docstring=True) +def stats_titulaire(titulaire_id: str) -> dict: + """Statistiques agrégées d'un titulaire (entreprise) par identifiant. + + Retourne nombre de marchés remportés, montant total, répartition + annuelle, principaux acheteurs et principaux codes CPV. + """ + track_mcp_tool("stats_titulaire") + return queries.compute_org_stats("titulaire", titulaire_id) + + +@mcp_enabled(name="rechercher_marches", expose_docstring=True) +def rechercher_marches( + acheteur_id: str | None = None, + titulaire_id: str | None = None, + cpv: str | None = None, + objet_contient: str | None = None, + montant_min: float | None = None, + montant_max: float | None = None, + date_min: str | None = None, + date_max: str | None = None, + departement: str | None = None, + page: int = 1, + filtres_avances: dict | None = None, +) -> dict: + """Recherche paginée de marchés publics (DECP). + + Filtres nommés : acheteur_id, titulaire_id, cpv (code CPV, correspondance + partielle), objet_contient (texte de l'objet), montant_min, montant_max, + date_min / date_max (format YYYY-MM-DD, sur dateNotification), + departement (code département de l'acheteur). + filtres_avances : dict optionnel {"colonne__operateur": valeur} pour les + besoins pointus (mêmes colonnes/opérateurs que l'API REST colibre). + page : numéro de page (50 résultats par page). + Retourne {meta: {page, page_size, total}, marches: [...]}. + """ + track_mcp_tool("rechercher_marches", query=objet_contient) + return queries.search_marches( + acheteur_id=acheteur_id, + titulaire_id=titulaire_id, + cpv=cpv, + objet_contient=objet_contient, + montant_min=montant_min, + montant_max=montant_max, + date_min=date_min, + date_max=date_max, + departement=departement, + page=page, + filtres_avances=filtres_avances, + ) diff --git a/tests/mcp/test_tools.py b/tests/mcp/test_tools.py new file mode 100644 index 0000000..18ac863 --- /dev/null +++ b/tests/mcp/test_tools.py @@ -0,0 +1,33 @@ +from src.mcp import tools + + +def test_all_four_tools_are_callable(): + for name in ( + "rechercher_organisations", + "stats_acheteur", + "stats_titulaire", + "rechercher_marches", + ): + assert callable(getattr(tools, name)) + + +def test_rechercher_organisations_returns_list(): + result = tools.rechercher_organisations("ACHETEUR", "acheteur") + assert isinstance(result, list) + assert any(r["id"] == "123" for r in result) + + +def test_rechercher_marches_returns_meta(): + result = tools.rechercher_marches(acheteur_id="123") + assert result["meta"]["total"] >= 1 + + +def test_stats_acheteur_returns_stats(): + result = tools.stats_acheteur("123") + assert result["nb_marches"] >= 1 + assert result["identite"]["id"] == "123" + + +def test_stats_titulaire_returns_stats(): + result = tools.stats_titulaire("345") + assert result["nb_marches"] >= 1