{
  "title": "Réparer la recherche B2B à l'ère de l'IA",
  "excerpt": "J'ai audité 775 000 enregistrements produits alimentant la recherche pour quatre distributeurs B2B. Les géants du commerce de détail ont déjà publié comment réparer ce type de catalogues avec des LLM, mais la distribution industrielle n'a pas encore adopté cette approche. La voici, adaptée pour le CVC, la plomberie et l'électricité, avec des coûts réels.",
  "content_html": "<p>La recherche B2B a un secret bien gardé : l'algorithme de classement est rarement le problème.</p>\n<p>Quand les résultats sont mauvais, les équipes cherchent les leviers amusants. Les boosts, les synonymes, les embeddings, les rerankers, et récemment la compréhension de requête par LLM. J'ai moi-même actionné tous ces leviers. La semaine dernière, j'ai plutôt fait la chose peu glamour : j'ai extrait tous les enregistrements produits de quatre index de recherche en production — 775 051 enregistrements répartis sur quatre distributeurs B2B dans le CVC, la plomberie, l'approvisionnement industriel et les fixations — et j'ai audité les données elles-mêmes. J'ai utilisé douze agents IA fonctionnant en parallèle, chacun affecté à une dimension : couverture, qualité du texte, numéros de pièces, marques, prix, spécifications, catégories, médias, doublons.</p>\n<p>Ce qui en est ressorti serait banal pour quiconque a déjà travaillé sur un catalogue de distributeur, et choquant pour ceux qui ne l'ont jamais fait. Mais ce qui m'a marqué, c'est ceci : la solution est déjà publiée. Amazon, DoorDash et Instacart ont passé deux ans à expliquer, dans des blogs d'ingénierie publics, exactement comment ils utilisent les LLM pour nettoyer, étiqueter et enrichir des catalogues désordonnés à grande échelle. La distribution industrielle ne l'a pour la plupart pas remarqué. Ce qui est étrange, car ses catalogues sont plus désordonnés, ses requêtes ont plus de valeur (un entrepreneur commandant un condenseur à 4 000 $, pas un déjeuner à 12 $), et la taille de ses catalogues rend en réalité l'économie plus facile.</p>\n<p>Ce billet est donc ce guide pratique, adapté à la distribution industrielle : les problèmes de données qui cassent la recherche B2B, comment les trouver avec un agent de codage cet après-midi, comment les corriger avec des LLM incluant la catégorisation UNSPSC, et combien tout cela coûte. Spoiler sur le dernier point : moins que ce que vous imaginez, de deux ordres de grandeur.</p>\n<h2>Le guide pratique existe déjà, juste pas dans notre industrie</h2>\n<p>Ce que les entreprises de marketplace ont publié :</p>\n<ul>\n<li>DoorDash utilise des LLM avec génération augmentée par récupération pour <a href=\"https://careersatdoordash.com/blog/how-doordash-leverages-llms-for-better-search-retrieval/\">construire son graphe de connaissances produit et améliorer la récupération de recherche</a> : extraction automatisée de marques, extraction d'attributs et liaison d'entités sur des millions d'articles fournis par des commerçants.</li>\n<li>Amazon a construit <a href=\"https://www.amazon.science/blog/building-commonsense-knowledge-graphs-to-aid-product-recommendation\">COSMO</a>, un graphe de connaissances généré par LLM qui relie ce que les clients veulent dire (« chaussures pour femmes enceintes ») à ce que sont les produits (« chaussures antidérapantes »). Ils rapportent jusqu'à 60 % d'amélioration des performances de recommandation dans des tests hors ligne.</li>\n<li>Instacart a intégré des LLM dans sa stack de recherche pour <a href=\"https://tech.instacart.com/supercharging-discovery-in-search-with-llms-556c585d4720\">générer du contenu de découverte</a> et <a href=\"https://www.instacart.com/company/tech-innovation/building-the-intent-engine-how-instacart-is-revamping-query-understanding-with-llms\">reconstruire la compréhension de requête autour d'eux</a> — avec la génération lourde effectuée hors ligne par lots, précisément pour réduire les coûts.</li>\n</ul>\n<p>Le fil conducteur est facile à manquer : les gains des LLM se situent côté catalogue et hors ligne. Ces entreprises enrichissent les données avant qu'une seule requête n'arrive. Les systèmes de découverte se divisent en une partie hors ligne qui construit des artefacts (le catalogue, les index, les embeddings) et une partie en ligne qui récupère et classe par-dessus, et presque toute la valeur LLM publiée atterrit hors ligne. La couche de classement ne peut être meilleure que ce qu'on lui donne.</p>\n<p>Maintenant, imaginez un catalogue typique de distributeur CVC ou plomberie à côté de ça. Il est assemblé à partir d'exports ERP, de feuilles de calcul fournisseurs, de flux de groupes d'achat et d'un parseur CSV écrit il y a dix ans par quelqu'un. Les « titres » de produits sont des abréviations de facture écrites pour les magasiniers. Et le trafic de recherche qui le sollicite est aussi dense en intention que possible : la moitié en numéros de pièces exacts, le reste en jargon professionnel comme « 3/4 cxc 90 ell ». Honnêtement, je ne vois pas un meilleur environnement pour ce guide pratique. Les requêtes sont précieuses, les données sont réparables, et avec quelques centaines de milliers de références, un passage LLM complet sur le catalogue coûte des centaines de dollars. Pas des millions. Des centaines.</p>\n<p>Voici la structure de l'audit, car vous voudrez le reproduire :</p>\n<pre><code class=\"mermaid\">flowchart TD\n    D[\"Raw catalog dump&lt;br/&gt;(JSONL, one record per line)\"] --&gt; C1[\"Per-catalog coverage agents&lt;br/&gt;null rates, distributions, dead fields\"]\n    D --&gt; C2[\"Dimension agents&lt;br/&gt;text · part numbers · brands · pricing&lt;br/&gt;specs · categories · media · duplicates\"]\n    C1 --&gt; S[\"Synthesis: findings ranked by&lt;br/&gt;severity, with example records\"]\n    C2 --&gt; S\n    S --&gt; F[\"Fix pipeline: rules + LLM enrichment&lt;br/&gt;+ UNSPSC classification + ingest gates\"]</code></pre>\n<p>Une note sur le format avant les prompts. Tout ce qui suit fonctionne sur un dump générique : un objet JSON par ligne, chacun avec un `id` unique et les champs de l'enregistrement. Elasticsearch, OpenSearch, Solr, Algolia, Typesense, un PIM, un export de base de données brut — peu importe. Chaque plateforme peut produire cette structure, et chaque prompt ici s'exécute dessus.</p>\n<h2>Catégorie 1 : Défaillances silencieuses du pipeline</h2>\n<p>La pire chose que j'ai trouvée n'était pas de mauvaises données que quelqu'un aurait écrites. C'étaient des données que le pipeline détruisait en chemin, sans le dire à personne.</p>\n<p>Ces catalogues construisent leur texte de recherche principal au moment de l'ingestion, en utilisant une regex scriptée qui extrait les dimensions et unités des titres de produits. Sur les titres longs, la regex dépasse la limite de complexité du moteur, le processeur échoue, et l'enregistrement est quand même indexé — avec un champ d'erreur que personne ne lit et aucun champ de recherche principal.</p>\n<p><img src=\"/assets/images/b2b-search-silent-failures.png\" alt=\"Documents indexed with no primary search field, by distributor\"></p>\n<p>Sur l'ensemble du parc, 52 570 enregistrements restaient dans l'index, invisibles au chemin de requête principal. Sur le pire catalogue, c'est un produit sur sept. Aucun tableau de bord ne l'a détecté car rien n'avait échoué. Le pipeline renvoyait un succès toute la journée, tous les jours, pendant des mois.</p>\n<p>Deux autres découvertes de la même famille. Le miroir périmé : un champ détient la vérité (une carte de droits par compte), et un second champ aplati le reflète pour le filtrage. Sur un catalogue, le miroir avait dérivé sur 19,85 % des enregistrements — 31 000 produits actifs cachés à tort de la recherche filtrée par compte. Et le catalogue gelé : un index ne s'était pas rafraîchi depuis dix semaines tandis que ses frères se mettaient à jour quotidiennement. La disponibilité était surveillée. La fraîcheur ne l'était pas.</p>\n<p>Les correctifs sont ennuyeux et c'est tout l'intérêt. Alerte sur la couverture des champs dérivés (champ dérivé vide alors que sa source ne l'est pas). Ne maintenez pas deux représentations d'un même fait ; dérivez le champ filtrable au moment de l'écriture. Surveillez la fraîcheur des données par catalogue comme vous surveillez la disponibilité.</p>\n<p>Voici le prompt pour trouver tout cela dans votre propre catalogue (Claude Code ou `codex exec` ; la mécanique à la fin) :</p>\n<pre><code class=\"text\">Here is a dump of my product catalog as JSONL files in ./dump/ - one JSON\nrecord per line, each with a unique \"id\" and the product fields. My field/\nschema definition (if any) is in schema.json.\n\nWrite streaming Python (don't load it all in memory) to find silent pipeline\nfailures:\n1. Records carrying any error/exception field a pipeline stamped on them.\n2. For every DERIVED field (concatenations, *_search, *_normalized variants):\n   records where the derived field is empty but its obvious source field\n   is not.\n3. Pairs of fields that look like mirrors of each other (one nested/rich,\n   one flat/filterable): quantify how often they disagree.\n4. The distribution of updated/indexed timestamps: is any slice of the\n   catalog frozen while the rest refreshes?\n\nReport each finding with counts, % of catalog, 5 example ids, and whether the\naffected records are active/visible. Rank by severity.</code></pre>\n<h2>Catégorie 2 : Espaces réservés et sentinelles, ou : les données qui mentent</h2>\n<p>Les vérifications de nullité sont l'outil de qualité de données que tout le monde possède déjà. Les espaces réservés les vainquent, car un espace réservé est rempli. Il n'est simplement pas vrai.</p>\n<p>Mon exemple préféré : sur un catalogue de 313 000 références, le champ marque avait 741 valeurs distinctes et 99,99 % de remplissage. Ça paraît sain. La valeur la plus fréquente, sur 88,8 % de tous les enregistrements, était « Approved Vendor ». Un espace réservé ERP. La marque réelle la plus populaire couvrait 0,3 % du catalogue. Donc chaque facette de marque, chaque boost de marque et chaque réécriture sensible à la marque était effectivement morte pour les neuf dixièmes du catalogue, tandis que l'interface des facettes proposait joyeusement « Approved Vendor » comme filtre de marque n°1.</p>\n<p>Une fois qu'on commence à chercher, ce genre de choses est partout :</p>\n<ul>\n<li>Une sentinelle « pas de prix » de `99999999.000000` sur 55 % d'un catalogue. Le prix médian de cet index était de quatre-vingt-dix-neuf millions de dollars.</li>\n<li>Des noms de produits littéralement « unknown » sur 64 enregistrements, qui se classent ensuite pour la requête « unknown ».</li>\n<li>Un booléen `true` écrit dans un champ de description textuelle sur 1 516 produits en ligne. Le moteur l'a converti en chaîne « true », ce qui rendait ces produits trouvables en cherchant « true ».</li>\n<li>227 UPC stockés comme « 6.71E+11 » — la notation scientifique d'Excel, avec 51 produits sans rapport partageant ce même « identifiant » — plus 15 121 autres UPC manquant leur zéro initial. Excel frappe encore.</li>\n<li>Un catalogue de fixations dont le champ marque contenait des noms de gammes de produits (« C6L Lockbolts », « Tool Parts ») tandis que la marque réelle se trouvait un champ plus loin.</li>\n</ul>\n<p>La leçon que j'ai tirée de cette catégorie : auditez les distributions de valeurs top-N par champ, pas les taux de nullité. Un champ rempli à 100 % peut être à 88 % du garbage, et aucune vérification de nullité ne vous le dira jamais. Ensuite, mettez les sentinelles sur liste noire à l'ingestion, mappez-les vers de vraies valeurs nulles avec des indicateurs explicites `has_price` / `has_real_image`, assertez les types à la frontière et validez structurellement les identifiants. Traitez tout ce qui a un jour transité par une feuille de calcul comme suspect.</p>\n<pre><code class=\"text\">Same JSONL dump. For every field in my catalog, compute the top-20 value\ndistribution (full pass, streaming).\n\nFlag: (a) any single value covering &gt;10% of records - placeholder/sentinel\ncandidates like \"Approved Vendor\", \"unknown\", 99999999, 0.0; (b) values whose\ntype differs from the field's dominant type (booleans in text fields, floats\namong strings); (c) identifier fields (UPC/EAN/GTIN/part numbers): validate\nchecksum and length, flag scientific notation, stripped leading zeros,\nembedded whitespace/unicode, and identifiers shared by multiple records;\n(d) category-like or product-line values sitting in brand fields.\n\nFor each flag: count, % of catalog, 5 verbatim examples with ids, and a\none-line proposed ingest rule that would have rejected it.</code></pre>\n<h2>Catégorie 3 : Contenu appauvri</h2>\n<p>Les catalogues de distributeurs sont écrits par des ERP, pas par des merchandisers. Le texte est un raccourci de facture : `PROPRESS 2-1/2X1 CXC RED CPLG`, `HC HX 18X8 W`. Sur un catalogue, un tiers des noms de produits avaient moins de 60 % de mots du dictionnaire. Sur un autre, les champs nom et description bruts étaient 100 % nuls — le texte n'existait qu'à l'intérieur de blobs de recherche dérivés, donc tout ce qui lisait les champs bruts (titres de résultats, entrées d'embeddings, prompts de juge de pertinence) ne lisait rien et personne ne le savait.</p>\n<p><img src=\"/assets/images/b2b-search-field-coverage.png\" alt=\"Field coverage across four production B2B indices\"></p>\n<p>Cette carte de chaleur est mon artefact préféré de l'audit. Chacun de ces champs existe dans chaque schéma. Les chiffres indiquent quelle proportion contient réellement des données. La ligne qui me frappe est celle des descriptions ML : la couche d'enrichissement que les schémas promettaient sur chaque enregistrement n'était remplie que sur dix-huit documents sur 775 051. Dix-huit. Chaque branche « utiliser l'enrichissement si présent » dans le pipeline de scoring était un no-op silencieux. Le schéma est une aspiration ; seule la couverture est un fait.</p>\n<p>Cette catégorie est celle où le guide pratique publié s'applique le plus directement — l'extraction d'attributs de DoorDash, la génération hors ligne d'Instacart — et la section des coûts ci-dessous la chiffre. Mais quatre règles comptent dans un métier de numéros de pièces, apprises en partie de ce que la tentative d'enrichissement précédente avait mal fait :</p>\n<ul>\n<li>Étendez, ne remplacez pas. Générez le titre lisible par le client à côté de la chaîne de facture brute et indexez les deux. La requête du technicien de comptoir et celle du propriétaire doivent toutes les deux aboutir.</li>\n<li>Ne verbalisez jamais les codes. Ces dix-huit enregistrements pionniers ? Le générateur avait épelé les numéros de modèle en toutes lettres : « RGF one hundred eighty ». Aucun entrepreneur ne tapera jamais ça. Les codes restent verbatim ; les expansions sont des ajouts, pas des remplacements.</li>\n<li>Extrayez la structure pendant que vous y êtes. Le même passage qui réécrit un titre peut émettre `{size, material, connection_type}` pour les facettes.</li>\n<li>Le jargon professionnel est un problème de synonymes, pas de réécriture. `CXC`, `ELL`, `CPLG`, « t-stat » appartiennent à l'expansion de synonymes au niveau de l'analyseur, corrigés une fois. Ne payez pas pour les réécrire dans un demi-million d'enregistrements.</li>\n</ul>\n<pre><code class=\"text\">Same dump. Assess text quality of the customer-facing fields (name, short/long\ndescription) with a full streaming pass:\n\n1. Effective-title coverage: % of ACTIVE records with a non-empty display name.\n2. Readability: % ALL-CAPS names; % with under 60% dictionary-word tokens\n   (abbreviation salad); names under 10 chars; digits-only descriptions.\n3. Junk: HTML/CMS markup, encoding artifacts, embedded operational notes\n   (*** NOT A PHYSICAL ITEM ***), CSV column spillover.\n4. Duplication: identical short/long descriptions; boilerplate shared by 20+\n   SKUs; exact-length clusters (254/255/500 chars) indicating upstream\n   VARCHAR caps.\n5. Enrichment reality check: for every ML/enriched/embedding field the schema\n   promises, its actual coverage.\n\nNumbers, 5 verbatim examples each, severity, and which issues need an LLM\nenrichment pass vs. a pipeline fix vs. a synonym-layer fix.</code></pre>\n<h2>Catégorie 4 : Les numéros de pièces sont sacrés</h2>\n<p>La moitié du trafic de recherche B2B est quelqu'un qui tape un numéro de pièce. La correspondance exacte est tout le jeu là-bas, et elle échoue de manières silencieuses.</p>\n<p>Sur un catalogue, le numéro de pièce indexé était une référence numérique interne sur 99,99 % des enregistrements. Le numéro du fabricant — celui imprimé sur la boîte — n'existait qu'à l'intérieur des descriptions en texte libre. 183 000 produits injoignables par le numéro qu'un client taperait réellement.</p>\n<p>Un autre catalogue avait un système d'expansion de variantes : retirer les tirets, fusionner les segments, indexer les variantes pour qu'une recherche de numéro de pièce tolérante fonctionne. Bonne idée. Mais le générateur était combinatoire (jusqu'à 80 variantes par produit), et sur 11 826 enregistrements une variante générée d'un produit égalait exactement le numéro de pièce canonique d'un produit différent. Les fixations sont le cas brutal, car la ponctuation encode des dimensions physiques : `702-1-5/32` est une pièce de 1-5/32 pouces et `702-15/32` est une pièce de 15/32 pouces, et les deux se normalisent en `7021532`. Un client tape un numéro de pièce exact et obtient un pile ou face entre la bonne pièce et son cousin dimensionnel.</p>\n<p>Aussi dans cette catégorie : les références croisées concurrentes (« j'ai le numéro Ferguson, quel est le vôtre ? ») stockées comme blobs délimités par des pipes non fractionnés comme `19MU82|Fastenal 0269214|Ferguson M48222407`, dans des champs configurés comme non recherchables, donc un mouvement de vente B2B fondamental ne fonctionne tout simplement pas. Et les bifurcations d'espaces blancs — le même numéro de pièce indexé trois fois (simple, avec NBSP, avec espace de fin) — plus les jumeaux mojibake issus d'un double décodage UTF-8.</p>\n<p>Le principe qui corrige la plupart de cela : l'exact doit battre le flou structurellement, pas probabilistiquement. Une clause exacte préservant la ponctuation scorée strictement au-dessus de tout niveau normalisé, jamais un match plat où une variante peut égaler un hit canonique. Vérifiez les variantes générées par rapport à l'espace des numéros de pièces canoniques avant l'indexation et supprimez les collisions. Normalisez Unicode avant de dériver les identifiants d'enregistrement. Séparez les blobs de références croisées en tableaux ; c'est une seule ligne de code d'ingestion qui débloque toute une classe de requêtes.</p>\n<pre><code class=\"text\">Same dump. My users search by part number; audit exact-match integrity:\n\n1. Which PN-ish fields exist (part_number, mpn, alt/competitor/customer PNs,\n   UPC), their coverage, and which are configured unsearchable while holding\n   real data.\n2. Records whose only searchable PN token is an internal SKU (real\n   manufacturer PN appears only inside description text - show the pattern).\n3. If there's a PN-variant/keyword expansion field: find every generated\n   variant that equals a DIFFERENT record's canonical PN (normalize:\n   lowercase, strip punctuation). Show colliding pairs - especially\n   fraction-bearing fastener sizes.\n4. PNs differing only by whitespace/invisible chars/case from another\n   record's PN (duplicate identity forks).\n5. Multi-value cross-reference fields stored as delimited blobs instead of\n   arrays.\n\nCounts, examples, and for each issue the ingest rule or query-builder change\nthat fixes it.</code></pre>\n<p>Quelques choses qui ne rentraient pas ci-dessus mais méritent une phrase : un générateur de synonymes qui transformait n'importe quel nombre en termes de calibre de fil, donc `REF#259286` devenait « 259286 awg » et un filetage de vis `#8-32` devenait « 8 gauge wire » (des milliers d'enregistrements correspondent désormais à des requêtes électriques avec lesquelles ils n'ont rien à voir) ; des indicateurs internes d'une plateforme e-commerce indexés comme spécifications de produit recherchables, 2,1 millions de paires clé-valeur indésirables ; et 18 champs de schéma remplis sur zéro enregistrement. Les générateurs d'expansion ont besoin de portes de contexte. Le schéma mort est une fausse promesse sur laquelle quelqu'un finira par construire.</p>\n<h2>La couche manquante : UNSPSC, et les produits qui ne savent pas ce qu'ils sont</h2>\n<p>Il y a un cinquième problème qui mérite sa propre section, car c'est là que la distribution industrielle est la plus en retard par rapport aux marketplaces : la catégorisation.</p>\n<p>Dans mon audit, les champs de catégorie manquaient sur 83 % d'un catalogue. Les clés d'attributs n'avaient aucune taxonomie — 7 068 clés de spécifications distinctes sur un catalogue de 156 000 enregistrements, 64 % d'entre elles utilisées sur moins de dix produits, avec des dérives comme `horse_power` vs `horsepower` divisant le même attribut entre les facettes. Et le détail auquel je reviens sans cesse : deux des quatre catalogues avaient des champs UNSPSC dans leurs schémas, mappés et prêts, remplis sur exactement zéro enregistrement. Quelqu'un savait que la catégorisation comptait, a créé l'emplacement, et ne l'a jamais rempli. Je parierais de l'argent sur la raison : classer 300 000 références dans une taxonomie à la main représente un an de travail pour l'équipe catalogue, donc c'est resté éternellement sur la roadmap.</p>\n<p>Si vous êtes hors du B2B, UNSPSC est le United Nations Standard Products and Services Code, la taxonomie que parlent les systèmes d'approvisionnement. C'est la partie que la recherche grand public n'a pas : les systèmes d'achat de vos clients l'exigent. Les catalogues punchout, les plateformes d'e-procurement et les outils d'analyse des dépenses sont construits autour des codes UNSPSC. Un distributeur dont le catalogue porte des codes propres peut se brancher sur le système d'achat d'un entrepreneur ou d'un hôpital. Un distributeur sans codes est une liste de prix PDF avec une barre de recherche. En interne, c'est aussi ce qui alimente les facettes de catégorie, le classement par portée de catégorie (« un nombre qui s'analyse comme un calibre de fil ne devrait booster que les fils »), la déduplication inter-catalogues, et les portes de contexte pour chaque générateur d'expansion de la Catégorie 4.</p>\n<p>Et c'est désormais un traitement par lots. C'est la même forme de problème que DoorDash décrit en train de résoudre avec des LLM — étiquetage à haut volume contre un vocabulaire contrôlé — et l'approche se transpose directement :</p>\n<ul>\n<li>Classifiez hiérarchiquement, pas à plat. UNSPSC a quatre niveaux (segment, famille, classe, produit de base). Faites choisir le modèle parmi ~450 options pour la famille, puis la classe dans cette famille. Deux petits choix contraints valent mieux qu'un choix parmi 50 000, et vous pouvez alimenter la tranche de taxonomie pertinente dans le contexte comme DoorDash contraint la liaison d'entités à son vocabulaire.</li>\n<li>Arrêtez-vous d'abord au niveau classe. Les codes de classe déverrouillent déjà les facettes et l'intégration d'approvisionnement ; la précision au niveau produit de base peut venir plus tard là où elle gagne sa place.</li>\n<li>Routez par confiance. Un petit modèle prend les cas clairs, les enregistrements à faible confiance passent à un modèle plus gros, et les désaccords persistants vont dans une file humaine qui sert aussi d'ensemble d'évaluation.</li>\n</ul>\n<p>Le coût est presque gênant à écrire. La classification est une tâche à sortie courte : environ 300 tokens d'entrée par enregistrement et 30 en sortie. Sur Claude Haiku 4.5 au tarif Batch API, cela fait environ 17 centimes pour mille références. Tout mon parc de 775 000 enregistrements se classerait pour environ 130 $. La chose qui est restée sur la roadmap pendant des années parce qu'elle représentait un an de travail manuel coûte moins que le déjeuner d'équipe où vous en discuteriez.</p>\n<pre><code class=\"text\">You classify B2B distributor products into UNSPSC. Attached: the UNSPSC\nfamily list (level 2, ~450 entries) as reference data.\n\nFor each product record (title, description, brand, part number, any existing\ncategory text), output JSON:\n- unspsc_family: the 4-digit family code - choose ONLY from the attached list\n- family_confidence: high | low\n- rationale: one short phrase (e.g. \"copper press fitting -&gt; pipe fittings\")\n\nRules: judge from the product's function, not its brand. Trade shorthand:\nCXC/FPT/MPT are pipe connections, ELL is elbow, CPLG is coupling, a bare\nfraction+material is usually a fitting size. If the record is not a physical\nproduct (freight lines, upcharges, \"*** NOT A PHYSICAL ITEM ***\"), output\nNOT_A_PRODUCT. Use low confidence liberally - low-confidence records get a\nsecond pass with the class-level list.</code></pre>\n<h2>Le guide de nettoyage, avec la facture réelle</h2>\n<p>Le pipeline de correction a trois niveaux plus le travail de classification. L'astuce consiste à dépenser au bon niveau, car vous n'avez presque jamais besoin d'un appel LLM par enregistrement.</p>\n<pre><code class=\"mermaid\">flowchart LR\n    A[\"Tier 0 - Rules&lt;br/&gt;deterministic code&lt;br/&gt;~$0\"] --&gt; B[\"Tier 1 - LLM on unique values&lt;br/&gt;brands · spec keys · units&lt;br/&gt;tens of dollars\"]\n    B --&gt; C[\"Tier 2 - LLM per record&lt;br/&gt;titles · attributes · UNSPSC&lt;br/&gt;hundreds of dollars\"]\n    C --&gt; G[\"Ingest gates&lt;br/&gt;every fix becomes a validator\"]</code></pre>\n<p><strong>Le Tier 0 est du code déterministe et c'est essentiellement gratuit.</strong> Supprimez le HTML et décodez les entités. Transformez les sentinelles en vraies valeurs nulles avec des indicateurs explicites. Réparez les UPC (remplissage à gauche, rejet de la notation scientifique, validation du chiffre de contrôle). Normalisez Unicode pour les identifiants. Séparez les références croisées délimitées par des pipes. Supprimez les variantes de numéros de pièces qui entrent en collision avec le numéro canonique d'un autre produit. Supprimez le schéma mort. Un agent écrit ces scripts en une session, et ce niveau a corrigé environ la moitié de tout ce que mon audit a trouvé. Faites-le avant tout enrichissement, sinon vous paierez un modèle pour réécrire magnifiquement les titres de produits que votre pipeline a silencieusement abandonnés.</p>\n<p><strong>Le Tier 1 fait passer le LLM sur les valeurs uniques, pas les enregistrements.</strong> C'est l'astuce qui rend les problèmes de vocabulaire bon marché : mon pire catalogue avait 313 000 enregistrements mais seulement 741 chaînes de marque distinctes. La normalisation des marques est un seul travail sur 741 chaînes — regrouper les variantes de casse, mapper les sous-marques vers les maisons mères, signaler les espaces réservés — pas 313 000 appels. Même démarche pour les clés de spécifications et les suffixes d'unités. Chaque travail de vocabulaire coûte quelques dollars et sa sortie est une table d'alias statique que votre ingestion applique pour toujours, de manière déterministe. Comptez 20-50 $ pour l'ensemble du parc, dépensés principalement en passes de revue.</p>\n<pre><code class=\"text\">Attached: the full distinct-value distribution of my brand and manufacturer\nfields (value, record_count). Produce a normalization table:\n\ncanonical_brand, parent_manufacturer, [raw variants], confidence\n\nRules: cluster case/punctuation/whitespace variants; map sub-brands to parent\nmanufacturers as a separate column (don't merge); mark placeholder values as\nNULL_SENTINEL; mark product lines or categories misfiled as brands as\nNOT_A_BRAND. Flag uncertain clusters rather than guessing. Output CSV, then\nwrite a script that applies it at ingest and logs unmatched new values.</code></pre>\n<p><strong>Le Tier 2 est le passage par enregistrement</strong> : un titre lisible par le client, des expansions de recherche, des attributs structurés et un numéro de pièce fabricant récupéré pour chaque produit. Par référence c'est petit — environ 400 tokens d'entrée (l'enregistrement, plus des instructions partagées que le cache de prompt rend presque gratuites) et 250 en sortie. Deux leviers avant même de choisir un modèle : enrichissez ce qui est actif (sur mon pire catalogue seulement 14 % des enregistrements étaient actifs, une réduction de 86 % immédiate), et utilisez une API batch là où le fournisseur en a une, puisque l'enrichissement n'a pas d'exigence de latence et qu'Anthropic et OpenAI accordent tous deux 50 % de réduction pour le batch.</p>\n<p>J'ai chiffré le même travail sur tout ce que vous utiliseriez plausiblement en 2026 — Claude, <a href=\"https://openai.com/index/advancing-the-price-performance-frontier-with-gpt-5-6/\">les trois niveaux de GPT-5.6</a> (Sol, Terra, Luna), Kimi K3, GLM-5.2, Qwen3.5, et <a href=\"https://deepseek.ai/pricing\">DeepSeek V4</a>. Même charge de travail, tarifs publiés par les fournisseurs, remise batch appliquée là où elle existe :</p>\n<p><img src=\"/assets/images/b2b-search-enrichment-cost-by-model.png\" alt=\"Cost to enrich 775K SKUs across models\"></p>\n<p>J'ai dû vérifier ces chiffres deux fois car ils semblaient faux. La version la plus pessimiste de ce projet — un modèle de pointe, chaque référence, sans aucun filtrage — plafonne autour de 3 800 $. Le plancher est en dessous de cent dollars : DeepSeek V4-Flash enrichirait tout le parc pour environ le prix d'une bonne perceuse. La version que vous lanceriez réellement mélange les niveaux : un modèle économique (Haiku, Luna, Qwen Flash, DeepSeek) pour la majorité mécanique, un modèle de milieu de gamme (Sonnet, Terra, GLM-5.2) pour la queue de salade d'abréviations que le petit modèle signale comme faible confiance, et un modèle de pointe vérifiant un échantillon de 1-2 % comme contrôle qualité. Cela atterrit à quelques centaines de dollars pour les références actives quel que soit le fournisseur choisi.</p>\n<p>Deux mises en garde que le graphique ne peut pas montrer. Les modèles bon marché ne sont bon marché que si leur sortie survit à votre évaluation — lancez la comparaison sur 200 échantillons avant de vous engager, car un modèle économique qui malaxe 5 % des numéros de pièces coûte plus cher que le modèle de pointe qui ne le fait pas. Et votre catalogue est une donnée concurrentielle : prix, références croisées et numéros de pièces clients sont dans ces enregistrements, donc vérifiez les conditions de rétention et d'entraînement des données de chaque fournisseur avant d'envoyer 775 000 enregistrements au point de terminaison le moins cher de la liste. Pour beaucoup de distributeurs, cette seule vérification élimine d'emblée certains fournisseurs, et les options à poids ouvert (DeepSeek, GLM, Qwen, Kimi) ont la propriété supplémentaire que vous pouvez les auto-héberger si les données ne peuvent pas du tout sortir.</p>\n<p>Ajoutez le passage UNSPSC à ~130 $ (qui évolue de la même manière selon les modèles — sur le niveau économique, ça tombe à de la petite monnaie) et le travail de vocabulaire, et toute la transformation du catalogue se chiffre entre quelques centaines et quelques milliers de dollars, contre un travail qui représentait auparavant l'année d'une équipe de merchandising. C'est la même économie de traitement hors ligne par lots qu'Instacart décrit, juste à l'échelle du catalogue industriel où les chiffres deviennent assez petits pour les mettre sur une carte de crédit.</p>\n<p>Voici à quoi cela ressemble sur un enregistrement réel de mon audit :</p>\n<pre><code class=\"text\">IN:  part_number: 1678619\n     short_desc:  PROPRESS 2-1/2X1 CXC RED CPLG 20685\n     brand:       (placeholder)\n\nOUT: display_title:      2-1/2\" x 1\" ProPress Copper Reducing Coupling\n     search_expansions:  [\"copper press fitting\", \"reducing coupling\",\n                          \"press x press coupling\"]\n     attributes:         {size_1: {value: 2.5, unit: in},\n                          size_2: {value: 1, unit: in},\n                          material: copper, connection: press}\n     extracted_mpn:      \"20685\"\n     unspsc_family:      4017 (pipe fittings)\n     confidence:         high</code></pre>\n<p>Regardez la ligne `extracted_mpn`. Le même passage récupère les numéros de pièces fabricant qui étaient auparavant enfouis dans le texte de description — sur un de mes catalogues, ce sont 183 000 produits qui deviennent trouvables par le numéro imprimé sur la boîte. Cela à lui seul paie le traitement par lots.</p>\n<pre><code class=\"text\">You enrich B2B distributor product records for search. For each input record\n(raw ERP name, descriptions, brand, part number, category) produce JSON:\n\n- display_title: customer-readable, &lt;=80 chars, Title Case. Expand trade\n  abbreviations (CXC -&gt; copper x copper connection, ELL -&gt; elbow, CPLG -&gt;\n  coupling, RED -&gt; reducing). Keep every part number, model code, and\n  dimension VERBATIM as typed - never spell codes out in words.\n- search_expansions: 2-5 phrases a customer might type that don't appear in\n  the raw text (plain-English product type, common trade names). Never invent\n  specs absent from the source.\n- attributes: {name: {value, unit}} extracted ONLY from the source text.\n- extracted_mpn: manufacturer part number if present in the description text\n  (usually the token after the brand) and absent from the PN fields, else null.\n- confidence: high | low. Use low whenever you had to guess; a low answer\n  routed to review beats a confident hallucination.</code></pre>\n<p>Deux notes de production : mettez les instructions partagées dans un préfixe de prompt mis en cache (le cache réduit le côté entrée jusqu'à 90 % en plus de la remise batch), et utilisez des sorties structurées avec un schéma JSON pour ne jamais payer de taxe d'échec d'analyse.</p>\n<p><strong>Le dernier niveau est celui que les gens sautent, et c'est celui qui se cumule.</strong> L'audit vaut pour un nettoyage. Les validateurs qu'il génère valent pour chaque futur flux. Terminez la session de correction avec :</p>\n<pre><code class=\"text\">Turn every issue we just fixed into an automated check that fails the ingest\npipeline loudly: derived-field coverage assertions, sentinel blocklists, type\nassertions on text fields, identifier checksum validation, PN-variant\ncollision checks, id hygiene rules, UNSPSC coverage tracking, and a data-\nfreshness alarm per catalog. Emit them as tests CI runs against a sample of\nevery new feed.</code></pre>\n<p>Sautez cette étape et les mêmes exports ERP régénéreront les mêmes ordures d'ici un trimestre, et vous paierez le nettoyage deux fois. Je le sais car deux des bugs que mon audit a trouvés avaient clairement été corrigés auparavant, et étaient revenus.</p>\n<h2>Exécuter les prompts</h2>\n<p>Une étape de configuration : exportez votre catalogue en JSONL, un objet JSON par ligne avec un `id` unique et les champs de l'enregistrement. Quelle que soit votre plateforme — Elasticsearch, OpenSearch, Solr, Algolia, Typesense, un PIM, la base de données derrière tout ça — demandez simplement à l'agent d'écrire l'exporteur :</p>\n<pre><code class=\"text\">Write a script that exports every product record from my catalog [describe:\nsearch index name / database table / PIM export] to ./dump/records-{n}.jsonl\nin 50k-record chunks - one JSON object per line with a unique \"id\" plus all\nfields - and my field/schema definition (if the platform has one) to\nschema.json. Verify the exported count matches the source count.</code></pre>\n<p>Avec Claude Code, déposez n'importe quel prompt de catégorie dans une session dans ce répertoire. Il écrit et exécute l'analyse lui-même, des passes complètes sans excuse d'échantillonnage, et revient avec des preuves au niveau de l'enregistrement. Quand vous dites « maintenant corrigez », la même session produit le validateur d'ingestion, le script de backfill et le travail d'enrichissement par lots. Lancez les catégories comme des sessions parallèles ou des sous-agents ; c'est comme ça que j'ai lancé les miennes, et tout l'audit a pris environ douze minutes de temps réel. Avec Codex, `codex exec` gère les passes d'analyse en lecture seule avec les mêmes prompts ; gardez les corrections côté écriture dans une session que vous revoyez.</p>\n<p>Trois règles que j'ai apprises à mes dépens :</p>\n<ol>\n<li>Exigez des passes complètes et des preuves. « Analysez ces données » invite à l'échantillonnage et aux impressions. Demandez des comptes exacts et cinq identifiants d'enregistrements d'exemple par découverte, et chaque affirmation devient vérifiable.</li>\n<li>Les règles avant l'enrichissement. Corrigez la vérité avant d'ajouter de la prose, sinon vous hallucinerez des marques pour les 88 % d'enregistrements étiquetés « Approved Vendor ».</li>\n<li>Échantillonnez, évaluez, puis lancez par lots. Ne déclenchez jamais un lot de 775 000 enregistrements à partir d'un prompt non validé. 200 échantillons, une passe de scoring, puis passage à l'échelle. Le harnais d'évaluation coûte une session d'agent et dérisque toute la dépense.</li>\n</ol>\n<h2>Les métiers méritent une recherche de qualité marketplace</h2>\n<p>Amazon, DoorDash et Instacart n'ont pas publié leur travail LLM sur les catalogues par charité. Ils l'ont publié parce que les techniques sont générales et que le fossé concurrentiel est dans l'exécution. Et les techniques se transposent à la distribution industrielle mieux que presque partout ailleurs où je puisse penser : les catalogues sont assez petits pour qu'un passage complet soit bon marché, les données sont assez mauvaises pour que la marge de progression soit énorme, les requêtes transportent de l'argent réel, et le monde de l'approvisionnement fonctionne déjà sur une taxonomie qu'un traitement par lots peut désormais remplir pour environ 130 $.</p>\n<p>Sur les dizaines de problèmes que mon audit a révélés sur quatre catalogues de distributeurs, pas un seul n'était visible depuis la couche de classement. Tous la dégradaient. La chaîne d'approvisionnement qui alimente ces index — les exports ERP, les feuilles de calcul fournisseurs, les anciens parseurs CSV — est exactement ce que le guide pratique publié corrige. Les distributeurs qui l'appliqueront auront une recherche de qualité marketplace sur des catalogues où leurs concurrents ne trouvent toujours pas une bobine de condenseur.</p>\n<p>La question « est-ce que quelque chose ne va pas avec mes données ? » coûtait autrefois deux semaines du temps de quelqu'un, c'est pourquoi personne ne la posait. Elle coûte désormais un prompt. Posez-la.</p>",
  "source_hash": "sha256:729970b39c1b22c143ba55272659d6cc431b0f8889282089a4ea4eaa71f75000",
  "model": "z-ai/glm-5.2",
  "generated_at": "2026-08-06T09:54:08.643436+00:00"
}