Documentation bot.ecorce.app
Version 1 · BROUILLON · base https://bot.ecorce.app
Cette page est générée à partir du registre unique des composants : tout ce qui est décrit ici existe, rien d’autre.
Les trois niveaux
- read : lecture (publique, limitée en débit)
- prepare : préparer (jeton d’action d’une personne ; brouillons seulement)
- commit : confirmer (humain seulement, jamais un bot)
Cycle de commande
États d’une commande : draft → confirmed → sent → acknowledged (et cancelled si remplacée ou refusée).
- draft : créé par un bot avec create_draft_order.
- confirmed : seulement par le geste de l’épicier sur /ok/{code}.
- sent et acknowledged : posés à la main (v1). v1 ne contacte aucun distributeur : la confirmation produit un YAML à vérifier et l’envoi reste manuel.
Plafonds : 50 par ligne, 40 lignes, 400 unités. Les prix ne viennent que de la liste Canadawide chargée (jamais acceptés en entrée).
Prix : données de test
Prix au 5 août 2025, données de test / Prices as of August 5, 2025, test data. Source : Canadawide daily sheet. Montants en dollars canadiens (CAD), par unité de vente telle qu’écrite dans la description du produit (caisse, sac, kilo… : le prix n’est pas « par kilo » sauf si la description le dit), sans taxes, sans rabais, sans prix d’achat groupé. Chaque prix est accompagné de prices_as_of.
- Ce sont des données de test. Les prix de fruits et légumes changent chaque jour : un assistant ne doit jamais les présenter comme actuels, comme une soumission ni comme engageants.
- Les prix sont figés dans le brouillon au moment de sa création, puis dans le YAML à la confirmation (prix_au, prix_unitaire, total_ligne, sous_total).
- Environ 1449 produits commandables sur 1535 lignes (les lignes hors fruits et légumes, comme vinaigrettes ou tofu, ne sont pas commandables ici). Le SPN ne couvre qu’une partie des codes :
spnest null quand il n’y a pas de nom SPN. - Pour recharger une liste plus récente :
node scripts/load_pricelist.mjs fichier.xlsx(voir le README).
Le SPN comme langue pivot
Le SPN (Produit | Variété | Origine | Catégorie | Taille | Emballage | Méthode, le bio étant dans Méthode) est la langue commune entre les fournisseurs et les épiceries :
- Aller : chaque ligne du fournisseur (supplier_id + sku) est rattachée à UN article SPN, avec une confiance (high, medium, low) et une source : spn (code retrouvé dans le SPN), rules (règles du SPN appliquées au libellé), ai (proposé par l’IA, à revoir) ou manual (corrigé à la main). Plusieurs lignes peuvent mener au même article.
- Regrouper : les articles SPN forment des produits (produit + variété + bio, variantes d’orthographe fusionnées), puis des familles (produit + bio). Aujourd’hui : 1248 articles SPN, 709 produits, 233 familles. Base : SPN du 2026-08-27 ; les articles qui manquent sont proposés (fichier spn-proposed-additions.json), jamais ajoutés au SPN en ligne sans Simon.
- Commander en SPN : set_cart accepte un article SPN (spn_id) ou un produit (product_id) avec une quantité approximative (unités de vente, kg, lb ou unités). Les demandes de plusieurs épiceries pour le même article s’additionnent : c’est ce qui rend l’achat groupé possible.
- Retour : resolve_to_supplier retraduit la commande SPN en lignes réelles du fournisseur : la ligne au total le plus bas qui couvre la quantité (arrondie à l’unité de vente supérieure), avec la raison, les solutions de rechange et la répartition par épicerie. Calcul seulement, rien n’est commandé.
- Unités : une quantité en kg, lb ou unités n’est convertie que si le format l’écrit (25LBS, 15KG, 14X6UN). « 10X1 », « 24 », « caisse » ou « 10-12 UN » ne donnent aucun poids ni nombre : la réponse le dit et demande des unités de vente.
- Relire et corriger : list_mappings et get_spn_item montrent chaque correspondance. Une correction se fait dans data/mapping-overrides.json (source manual), puis on relance la normalisation.
Charger une nouvelle liste (le flux de téléversement)
- Aujourd’hui (Simon ou un développeur) :
node scripts/load_pricelist.mjs fichier.xlsxlit la liste, écrit les prix, puis relance toute la normalisation (correspondances, produits, propositions SPN, rapport de couverture). On lance les tests, on relit le rapport, on déploie. - Plus tard (pas construit) : un fournisseur connecté téléverserait sa liste sur une page privée ; le serveur ferait la même chaîne dans une file d’attente, montrerait le rapport et les correspondances douteuses, et n’activerait la nouvelle liste qu’après la validation d’un humain. Aucun téléversement public n’existe.
Composant : Produits : recherche en langage naturel (pivot SPN)
Une recherche en français ou en anglais au lieu de 1 500 lignes : la liste fournisseur est traduite en articles SPN (Produit | Variété | Origine | Catégorie | Taille | Emballage | Méthode), regroupés par produit (produit + variété + bio). Chaque produit montre l’offre la moins chère, l’écart de prix, les origines, le bio et un best_fit expliqué. Les commandes peuvent s’exprimer en articles SPN avec une quantité approximative ; resolve_to_supplier les retraduit en lignes du fournisseur avec les raisons. Prix au 5 août 2025, données de test. Ni actuels ni engageants. / Prices as of August 5, 2025, test data. Not current, not binding.
Source : pivot SPN (src/data/pivot.js) + liste Canadawide chargée, généré par scripts/normalise_pricelist.mjs ; SPN de base du 2026-08-27 ; prix de test.
search_products
Niveau : read · lecture (publique, limitée en débit)
Cherche des produits en langage naturel (FR/EN), par ex. « carottes bio du Québec, environ 10 caisses », « apples from Ontario under $30 », « 20 kg d’oranges navel ». La requête devient des filtres (produit, variété, bio, origine, quantité approximative, prix max) ; les champs explicites ont priorité. Classement : pertinence, puis prix (ou total estimé si une quantité est donnée). Chaque résultat a best_fit (offre choisie + reasons) et, avec une quantité, packs_needed et est_total_cad. Aucune conversion d’unité n’est inventée : si le poids ou le nombre par format n’est pas écrit, la réponse le dit. « local », « du coin », « locally grown » (ou proximity=true) ne filtre pas : les offres sont classées par proximité (origin_tier : Québec 1, reste du Canada 2, États-Unis 3, ailleurs 4 ; code ambigu comme CA, NL, PE = niveau non confirmé avec une plage, placé après), puis par prix. Une origine explicite (« du Québec ») reste un filtre strict. Un prix très bas par rapport au même produit et format porte price_check « prix à vérifier » et n’est pas choisi comme best_fit sans que ce soit dit. Sans requête (ou level=family) : la liste des familles. Prix au 5 août 2025, données de test. Ni actuels ni engageants. / Prices as of August 5, 2025, test data. Not current, not binding.
Entrée : query:string, category:string(fruits|legumes|autres), organic:boolean, origin:string, proximity:boolean, max_price_cad:number, quantity:string, limit:integer, offset:integer, level:string(product|family), lang:string(fr|en)
POST /api/v1/tools/search_products
{"query":"carottes bio du Québec, environ 10 caisses"}
get_product
Niveau : read · lecture (publique, limitée en débit)
Un produit (produit + variété + bio) avec toutes ses offres (une par article SPN : origine, taille, format) et, sous chaque offre, les lignes du fournisseur (sku, description, prix, correspondance : confidence et source). Prix au 5 août 2025, données de test. Ni actuels ni engageants. / Prices as of August 5, 2025, test data. Not current, not binding.
Entrée : product_id*:string, lang:string(fr|en)
POST /api/v1/tools/get_product
{"product_id":"p_20107804"}
get_spn_item
Niveau : read · lecture (publique, limitée en débit)
Un article SPN (7 champs) et les lignes du fournisseur qui y correspondent, avec la confiance de chaque correspondance (high|medium|low), sa source (spn|rules|ai|manual), la confiance de l’origine et du format. Sert à vérifier et corriger la table de correspondance.
Entrée : spn_id*:string, lang:string(fr|en)
POST /api/v1/tools/get_spn_item
{"spn_id":"s_309883c494"}
suggest_substitutes
Niveau : read · lecture (publique, limitée en débit)
Solutions de rechange proches quand le produit voulu dépasse le budget, n’a pas la bonne origine ou n’existe pas en bio : même produit autre offre, même variété bio ou non, autre variété. Chaque suggestion dit ce qu’elle respecte (why) et ce qu’elle ne respecte pas (trade_offs), avec l’écart de prix. Donne product_id ou une requête. Prix au 5 août 2025, données de test. Ni actuels ni engageants. / Prices as of August 5, 2025, test data. Not current, not binding.
Entrée : product_id:string, query:string, organic:boolean, origin:string, max_price_cad:number, limit:integer, lang:string(fr|en)
POST /api/v1/tools/suggest_substitutes
{"query":"laitue romaine bio de Californie"}
resolve_to_supplier
Niveau : read · lecture (publique, limitée en débit)
Retraduit une commande exprimée en articles SPN (une ou plusieurs épiceries : buyer) en lignes réelles du fournisseur : regroupe par article SPN, choisit la ligne au total le plus bas qui couvre la quantité (arrondi à l’unité de vente supérieure), donne la raison, les solutions de rechange et la répartition par buyer. Une quantité en kg, lb ou unités n’est convertie que si le format l’indique ; sinon la ligne reste unresolved avec la raison. Calcul seulement : rien n’est commandé. Prix au 5 août 2025, données de test. Ni actuels ni engageants. / Prices as of August 5, 2025, test data. Not current, not binding.
Entrée : lines*:array, flex:string(pack|exact), lang:string(fr|en)
POST /api/v1/tools/resolve_to_supplier
{"lines":[{"spn_id":"s_309883c494","qty":2,"buyer":"epicerie-a"},{"spn_id":"s_309883c494","amount":15,"unit":"kg","buyer":"epicerie-b"}]}
list_mappings
Niveau : read · lecture (publique, limitée en débit)
Table de correspondance ligne fournisseur → article SPN, pour la relire : filtre par confidence (high|medium|low), source (spn|rules|ai|manual), q (texte). Chaque ligne a supplier_id, sku, description, spn_id, spn, confidence, source, origin_confidence et unit_confidence. Corrections : data/mapping-overrides.json (source manual), puis on relance la normalisation. Pas de prix ici.
Entrée : confidence:string(high|medium|low), source:string(spn|rules|ai|manual), q:string, limit:integer, offset:integer
POST /api/v1/tools/list_mappings
{}
Connaissances :
Composant : Commande de fruits et légumes
Le cycle hebdomadaire : catalogue (liste Canadawide, prix au 5 août 2025, données de test), panier avec totaux en CAD, brouillon de commande, geste de l’épicier. Un bot prépare ; seul l’épicier confirme, et rien n’est envoyé à un distributeur (v1). Les prix sont des données de test : ni actuels ni engageants.
Source : liste Canadawide chargée (xlsx), données de test, prix au 5 août 2025, données de test, prix publics, rechargeable par script ; ecorce-commande (réel, sans prix) seulement pour une personne avec un jeton de commande.
list_catalogue
Niveau : read · lecture (publique, limitée en débit)
Liste le catalogue de fruits et légumes de la liste Canadawide, avec les prix. Prix au 5 août 2025, données de test. Ni actuels ni engageants. / Prices as of August 5, 2025, test data. Not current, not binding. Chaque article a son sku (le code Canadawide), sa description, son nom SPN quand il existe, et price_cad avec prices_as_of. Filtres : q (texte), category (fruits|legumes|autres), bio, limit, offset.
Entrée : q:string, category:string(fruits|legumes|autres), bio:boolean, limit:integer, offset:integer
POST /api/v1/tools/list_catalogue
{}
get_cart
Niveau : prepare · préparer (jeton d’action d’une personne ; brouillons seulement)
Lit ton panier courant (lignes sku + quantité) avec prix unitaire, total par ligne et sous-total en CAD. Prix au 5 août 2025, données de test. Ni actuels ni engageants. / Prices as of August 5, 2025, test data. Not current, not binding.
Entrée : aucun paramètre
POST /api/v1/tools/get_cart
{}
set_cart
Niveau : prepare · préparer (jeton d’action d’une personne ; brouillons seulement)
Remplace tout le panier. Chaque ligne est : {sku, qty} (une ligne précise du fournisseur), {spn_id, qty} ou {spn_id, amount, unit} (un article SPN, quantité approximative : la ligne du fournisseur est choisie par resolve_to_supplier), ou {product_id, offer?, qty | amount + unit} (offer : spn_id, sku ou « best »). Plafonds après résolution : 50 unités de vente par ligne, 40 lignes, 400 unités en tout. Aucun champ de prix accepté : les prix viennent seulement de la liste chargée. Renvoie le panier résolu (ligne choisie, raison) avec les totaux en CAD (Prix au 5 août 2025, données de test). Ça ne commande rien.
Entrée : lines*:array, notes:string
POST /api/v1/tools/set_cart
{"lines":[{"sku":"0201-3010-0012","qty":2},{"spn_id":"s_309883c494","amount":20,"unit":"kg"}]}
create_draft_order
Niveau : prepare · préparer (jeton d’action d’une personne ; brouillons seulement)
Crée un BROUILLON de commande avec le panier courant (prix de la liste figés dans le brouillon, Prix au 5 août 2025, données de test) et renvoie un lien d’approbation /ok/{code} à envoyer à l’épicier. Ça ne passe jamais la commande : seul le geste de l’épicier la confirme, et même alors rien n’est envoyé à un distributeur.
Entrée : note:string
POST /api/v1/tools/create_draft_order
{}
get_order_status
Niveau : prepare · préparer (jeton d’action d’une personne ; brouillons seulement)
Donne l’état d’une commande (la dernière si order_id est omis) : draft, confirmed, sent ou acknowledged (ou cancelled). Après confirmation, renvoie le YAML à vérifier.
Entrée : order_id:string
POST /api/v1/tools/get_order_status
{}
confirm_order
Niveau : commit · confirmer (humain seulement, jamais un bot)
Confirme un brouillon. RÉSERVÉ À L’HUMAIN : lancé uniquement par le geste de l’épicier sur /ok/{code}. Marque la commande confirmed et produit le YAML à vérifier. N’envoie rien à un distributeur.
Non appelable par un bot (réponse 403 bot_cannot_commit). Lancé par le geste humain sur /ok/{code}.
Connaissances :
Composant : SPN : la norme de noms des fruits et légumes
Lecture seule. Cherche un produit dans la table de conversion Canadawide → SPN publiée par Écorce.
Source : ecorce-spn, réel, public, lecture seule.
spn_search
Niveau : read · lecture (publique, limitée en débit)
Cherche des produits dans le SPN par texte libre (nom SPN, libellé Canadawide, SKU, marque). Renvoie le nom SPN en 7 champs. Le SPN ne contient pas de prix (ils sont dans list_catalogue, avec leur date).
Entrée : q*:string, limit:integer, in_scope_only:boolean
POST /api/v1/tools/spn_search
{"q":"texte"}
spn_lookup
Niveau : read · lecture (publique, limitée en débit)
Trouve UN produit du SPN par SKU Canadawide exact (sku) ou par libellé Canadawide exact (name). Donne les 7 champs du nom SPN.
Entrée : sku:string, name:string
POST /api/v1/tools/spn_lookup
{}
Connaissances :
Composant : Connaissances : Écorce, PivoHub, achats groupés
Courts documents publics en français (BROUILLON, à valider par Simon). Les outils ask_* cherchent dedans ; aucune donnée confidentielle, aucun CRM, aucune condition de contrat.
Source : kb/*.md dans le Worker, statique, versionné, brouillon.
ask_ecorce
Niveau : read · lecture (publique, limitée en débit)
Pose une question sur Écorce (qui, quoi, chiffres publics, rythme des commandes). Répond avec des extraits du pack de connaissances (brouillon).
Entrée : question*:string
POST /api/v1/tools/ask_ecorce
{"question":"texte"}
ask_pivohub
Niveau : read · lecture (publique, limitée en débit)
Pose une question sur PivoHub (son rôle auprès d’Écorce). Répond avec des extraits du pack de connaissances (brouillon).
Entrée : question*:string
POST /api/v1/tools/ask_pivohub
{"question":"texte"}
group_buying_info
Niveau : read · lecture (publique, limitée en débit)
Explique les achats groupés (commandes mises en commun). La question est facultative : sans elle, renvoie le résumé.
Entrée : question:string
POST /api/v1/tools/group_buying_info
{}
Connaissances :
Points d’entrée
- GET
/llms.txt: ce résumé pour LLM - GET
/start.txt: démarrage pas à pas - GET
/docs: documentation (HTML) ; /docs.md en Markdown - GET
/api/v1: catalogue JSON des composants et outils - GET
/openapi.json: OpenAPI 3.1 - POST
/mcp: serveur MCP (JSON-RPC : initialize, tools/list, tools/call) - POST
/api/v1/connect: démarre la connexion (device code) ; la personne approuve sur /c/{user_code} - POST
/api/v1/connect/token: récupère le jeton d’action une fois approuvé - DELETE
/api/v1/tokens/me: révoque le jeton avec lequel tu appelles - GET
/api/v1/health: état du service
Erreurs
- 400 bad_input, 401 no_token, 403 bot_cannot_commit, 404 unknown_tool, 409 conflit d’état, 429 rate_limited, 502 upstream_*.