De photos brutes à un inventaire multicanal : concevoir un pipeline d’ingestion robuste
Retour d’architecture sur un pipeline qui identifie des objets à partir de photos, prépare leurs annonces et synchronise leur cycle de vie sur plusieurs marketplaces.
De photos brutes à un inventaire multicanal
Créer une annonce à partir de quelques photos paraît simple : reconnaître l’objet, générer un titre, estimer un prix, puis appeler l’API d’une marketplace. Cette vision fonctionne pour une démonstration. Elle devient insuffisante dès que l’on traite une caisse entière, que les photos arrivent dans le désordre ou que le même objet doit être diffusé sur plusieurs canaux.
Le vrai problème n’est alors plus la génération de contenu. C’est la maîtrise du cycle de vie d’un objet physique unique, depuis des entrées imparfaites jusqu’à sa vente, sans mélange, doublon ni collision de stock.
Cet article décrit d’abord un pipeline opérationnel allant d’un dossier de photos à des brouillons d’annonces. Il propose ensuite son évolution vers une architecture multicanale dans laquelle une base interne reste la source de vérité, tandis que des adaptateurs synchronisent eBay, Leboncoin ou d’autres distributeurs.
Idée directrice : l’IA peut produire des observations et des propositions. L’identité, le stock et les transitions irréversibles doivent rester sous le contrôle de règles applicatives explicites.
Une photo, un objet et une annonce sont trois identités différentes
Une caisse de livres illustre bien le problème. Un même livre peut être représenté par sa couverture, son dos, sa tranche, sa page de titre et plusieurs pages intérieures. À l’inverse, deux ouvrages proches peuvent partager un auteur, une collection, une reliure ou une esthétique presque identique.
Il faut donc distinguer au minimum :
- le fichier média, identifié par son contenu et son hash ;
- l’objet physique, ici un exemplaire unique pouvant avoir plusieurs photos ;
- la fiche canonique, qui rassemble titre, état, attributs et prix ;
- l’annonce distante, propre à un fournisseur et à ses contraintes ;
- la vente, qui consomme ou réserve l’unité de stock.
Confondre ces identités crée les bugs les plus coûteux : photos rattachées au mauvais livre, doublons d’annonces, modification d’un produit depuis une donnée spécifique à une marketplace ou double vente d’un objet disponible à un seul exemplaire.
Le pipeline initial : transformer une caisse en livres vérifiables
La première version du système traite correctement un livre lorsque ses photos sont déjà regroupées. L’extension à une caisse ajoute donc une étape préalable : retrouver les ensembles de photos correspondant à chaque objet, sans dépendre de l’ordre du dossier, des noms de fichiers ou de séparateurs artificiels.
1. Inventorier des entrées non fiables
Chaque image est d’abord traitée comme une entrée non fiable :
- format réel vérifié par décodage, et pas seulement par extension ;
- limites sur le nombre de fichiers, leur poids et leurs pixels décompressés ;
- correction locale de l’orientation EXIF sans modifier l’original ;
- calcul d’un hash pour l’identité, la reprise et la traçabilité ;
- lecture du timestamp de capture EXIF lorsqu’il existe ;
- conservation des originaux jusqu’à la validation du résultat.
Les dates du système de fichiers ne sont pas utilisées comme signal métier : elles changent trop facilement lors d’une copie, d’un export ou d’un passage par le cloud.
2. Décrire avant de regrouper
Un modèle multimodal économique décrit les images par lots selon un schéma fermé. Il ne choisit aucun chemin et ne déplace aucun fichier. Pour chaque photo, il extrait notamment :
- le type de vue : couverture, dos, tranche, page de titre, intérieur ;
- les éléments bibliographiques visibles : titre, auteur, éditeur, ISBN ;
- les marques distinctives : reliure, défauts, motifs, annotations ;
- la présence réelle d’un livre et la confiance de l’observation.
Ces observations structurées alimentent ensuite un graphe parcimonieux de candidats. Deux images peuvent devenir candidates grâce à un ISBN identique, un recouvrement de texte, des indices visuels compatibles ou une proximité temporelle.
Le timestamp est volontairement un indice souple. Deux photos prises à quelques minutes d’intervalle ont de bonnes chances de montrer le même objet, mais deux livres différents peuvent aussi avoir été photographiés à la suite. La proximité temporelle ouvre donc une vérification ; elle ne déclenche jamais une fusion à elle seule.
3. Préférer une séparation de trop à un mauvais mélange
Chaque paire ou groupe candidat passe par une vérification visuelle ciblée. Une fusion n’est acceptée que sur preuve positive. Un conflit fort (deux ISBN différents, par exemple) bloque le rapprochement. Une décision incertaine ne crée aucune relation.
Ce choix optimise la précision avant le rappel : une photo isolée dans la file unassigned coûte une vérification humaine ; deux livres mélangés dans une même annonce peuvent créer une mauvaise description, un prix incohérent et une vente litigieuse.
Le résultat est une partition complète et traçable :
- groupes considérés comme sûrs ;
- groupes signalés pour revue ;
- photos non assignées ;
- images ignorées car aucun objet exploitable n’y est détecté.
4. Garder un humain à la frontière irréversible
L’interface traduit directement cette séquence d’entrées-sorties : importer, regrouper, vérifier, puis créer. Les groupes sûrs peuvent être présélectionnés ; les cas ambigus ne le sont jamais. Les photos non assignées et ignorées restent visibles, mais elles ne sont pas envoyées silencieusement au pipeline suivant.
Après confirmation, chaque groupe devient l’entrée du pipeline unitaire :
- analyse détaillée de l’objet et de son état ;
- construction des attributs de catégorie ;
- recherche de comparables et estimation du prix ;
- préparation fidèle des photos ;
- upload des médias ;
- création d’un brouillon, sans publication automatique.
Les branches « métadonnées et prix » et « préparation des photos » peuvent être exécutées en parallèle, car elles ne dépendent l’une de l’autre qu’au moment de composer le brouillon.
Ce que cette première architecture résout déjà
Quatre propriétés comptent davantage que le choix précis du modèle :
- La prudence : aucune faible ressemblance ne suffit à fusionner deux objets.
- La reprise : observations et décisions coûteuses sont mises en cache à partir des hashes, du modèle et de la version du prompt.
- La traçabilité : chaque média apparaît exactement une fois dans le manifeste final, avec sa disposition et les décisions associées.
- La séparation des responsabilités : le modèle observe, le cœur métier décide, l’interface fait confirmer, puis un adaptateur parle au fournisseur.
Cette architecture est suffisante tant qu’une marketplace reste le seul canal de diffusion et que ses brouillons peuvent jouer le rôle d’inventaire distant. Elle atteint cependant une limite structurelle dès qu’un objet unique est publié à plusieurs endroits.
Passer d’un pipeline de publication à un système d’inventaire
Dans un système multicanal, eBay, Leboncoin ou un autre distributeur ne doivent pas devenir la source de vérité de l’objet. Chaque plateforme possède ses catégories, ses états et ses identifiants. Aucune n’a une vue fiable des autres.
La base interne doit porter l’état canonique : identité de l’objet, unité de stock, médias, prix de référence, cycle de vie, annonces distantes et vente. L’applicatif lit cette base ; les fournisseurs en reçoivent des projections adaptées à leurs contrats.
L’architecture cible combine trois patterns classiques :
- ports et adaptateurs pour isoler les particularités de chaque marketplace ;
- transactional outbox/inbox pour synchroniser la base et les appels distants sans perdre ni rejouer dangereusement une intention ;
- réconciliation périodique pour corriger la dérive inévitable entre les systèmes.
Un modèle de données centré sur l’objet physique
Pour des objets vendus à l’unité, le modèle minimal peut rester simple :
| Entité | Responsabilité |
|---|---|
media_asset | Original, hash, métadonnées EXIF et dérivés |
item | Fiche canonique indépendante des marketplaces |
inventory_unit | Exemplaire physique et disponibilité réelle |
listing | Projection de l’objet pour un fournisseur donné |
provider_binding | Identifiants distants, version et dernier état connu |
reservation | Blocage temporaire du stock pendant une transaction |
sale | Vente acceptée, fournisseur d’origine et données financières |
inbox_event | Événement entrant dédupliqué |
outbox_event | Intention de synchronisation à exécuter |
sync_attempt | Essai, latence, réponse normalisée et erreur |
Le contenu commercial peut varier par canal, mais l’identité et la quantité ne doivent pas être dupliquées dans chaque annonce. Pour un livre unique, inventory_unit.available_quantity vaut zéro ou un : cette contrainte doit être garantie transactionnellement par la base.
Publier sans double-write fragile
Une implémentation naïve effectue deux opérations successives : mettre à jour la base, puis appeler la marketplace. Si le processus tombe entre les deux, la base et le fournisseur divergent. Inverser l’ordre ne résout rien : l’annonce peut être créée alors que la transaction locale échoue.
La transactional outbox évite ce piège :
- une transaction locale modifie l’objet et insère une intention dans
outbox_event; - un worker lit cette intention et appelle l’adaptateur concerné ;
- l’adaptateur utilise une clé d’idempotence ou un identifiant métier stable ;
- la réponse distante met à jour
provider_bindinget l’état de l’annonce ; - un retry rejoue la même intention, pas une nouvelle création logique.
On ne cherche pas un hypothétique « exactly once » distribué. On accepte une livraison at least once, puis on rend chaque traitement idempotent.
L’adaptateur expose un vocabulaire commun, par exemple :
upsert_draft(item);publish(listing);update_price(listing, price);reserve_or_pause(listing);end_listing(listing, reason);fetch_status(binding);fetch_recent_sales(cursor).
Chaque fournisseur traduit ensuite ce contrat vers son API (ou vers le mode de synchronisation autorisé par la plateforme) sans faire remonter ses détails dans le domaine métier.
Gérer le cycle de vie plutôt que des booléens
Un champ published = true ne suffit pas. Une machine à états explicite rend les transitions observables et empêche les combinaisons impossibles.
Les annonces ont leur propre état (brouillon, publication en cours, active, pause en cours, terminée, erreur) mais elles restent des projections de l’unité de stock. Une annonce active ne rend jamais un objet disponible si inventory_unit indique déjà sold.
Une vente sur un canal doit fermer les autres
Lorsqu’une marketplace signale une vente, le traitement entrant suit lui aussi une inbox idempotente. L’événement brut est conservé, dédupliqué par son identifiant fournisseur, puis appliqué dans une transaction locale.
La transition available → sold doit utiliser un verrou, une comparaison de version ou une mise à jour conditionnelle. Deux événements concurrents ne peuvent ainsi pas consommer la même unité dans la base.
Cela ne rend pas le risque physique absolument nul : deux acheteurs peuvent valider presque simultanément sur deux plateformes externes avant que le retrait ne se propage. L’architecture réduit fortement cette fenêtre, détecte la collision et fournit un processus de résolution. La garantie maximale dépend ensuite des webhooks, mécanismes de réservation et délais permis par chaque fournisseur.
Les webhooks ne remplacent pas la réconciliation
Un webhook peut être perdu, livré en retard ou plusieurs fois. Certaines plateformes n’en proposent pas pour tous les événements. Un poller et une tâche de réconciliation restent donc nécessaires, même dans une architecture événementielle.
Le cron ne devrait pas contenir la logique métier. Il déclenche des jobs idempotents, protégés contre les exécutions concurrentes et observables dans le même système que les autres traitements.
Une cadence de départ raisonnable peut être :
| Cadence indicative | Contrôle | Résultat attendu |
|---|---|---|
| 1 à 5 minutes | Ventes et réservations sans webhook | Fermer rapidement les autres canaux |
| 10 à 15 minutes | États et quantités des annonces actives | Détecter les divergences de stock |
| Toutes les heures | Jobs bloqués, retries et réservations expirées | Réparer ou alerter |
| Chaque nuit | Réconciliation exhaustive | Trouver annonces orphelines et ventes manquées |
| Chaque matin | Synthèse opérationnelle | Donner les priorités de revue à l’équipe |
Ces fréquences doivent respecter les limites et les conditions d’utilisation de chaque plateforme.
Un dashboard orienté anomalies
Un bon dashboard ne se contente pas d’afficher le nombre d’annonces. Il doit répondre rapidement à quatre questions : que possède-t-on, où est-ce publié, qu’est-ce qui dérive et quelle action humaine est requise ?
Les métriques les plus utiles sont :
- objets disponibles, réservés, vendus et en revue ;
- annonces actives par fournisseur ;
- annonces distantes orphelines ou sans objet canonique ;
- divergences de prix, quantité ou statut ;
- latence entre une vente et le retrait des autres canaux ;
- âge du plus ancien événement inbox/outbox non traité ;
- taux d’échec et nombre de retries par adaptateur ;
- taux de photos non assignées et groupes soumis à revue ;
- coût moyen des appels IA par objet ingéré ;
- volume de ventes et marge par canal.
Les alertes peuvent être classées par impact :
- critique : collision de vente, objet vendu toujours actif ailleurs ;
- élevé : événement de vente non traité, retrait distant en échec ;
- moyen : annonce désynchronisée, job bloqué, réservation expirée ;
- informatif : hausse du taux de revue ou dérive du coût d’ingestion.
Chaque alerte doit pointer vers l’objet, les annonces concernées, la dernière transition réussie et l’action recommandée. Une alerte sans contexte ne fait que déplacer le travail de diagnostic vers l’opérateur.
Déployer cette évolution sans réécrire le pipeline
La migration peut rester incrémentale :
- Introduire le modèle canonique. Persister les objets et unités de stock avant de créer les brouillons du fournisseur déjà supporté.
- Encapsuler le premier fournisseur. Transformer l’intégration existante en adaptateur et stocker systématiquement les identifiants distants.
- Ajouter outbox, inbox et jobs de réconciliation. Rendre les retries idempotents avant d’augmenter le nombre de canaux.
- Brancher un second distributeur. Commencer par les brouillons, puis la publication, et enfin les retours de vente.
- Automatiser la fermeture multicanale. Mesurer la latence, tester les collisions et conserver un mode de résolution humaine.
- Construire alertes et dashboard. Les alimenter depuis les mêmes événements et états, plutôt que depuis des compteurs parallèles.
Le pipeline d’images n’a pas besoin de connaître eBay ou Leboncoin. Il produit un objet canonique et des médias validés. Les adaptateurs ne doivent pas savoir comment les photos ont été regroupées. Cette frontière permet de faire évoluer les modèles IA, l’interface et les fournisseurs indépendamment.
Conclusion
Le passage de quelques photos à une distribution multicanale ne se résume pas à ajouter une boucle sur une liste d’API. Il impose de décider où vit la vérité, qui possède le stock et comment chaque transition peut être rejouée, auditée ou compensée.
Le pipeline robuste se construit donc en deux étages :
- convertir des entrées visuelles incertaines en objets canoniques vérifiés ;
- projeter ces objets vers plusieurs canaux sans leur abandonner la maîtrise du cycle de vie.
L’IA accélère l’identification, la description et la tarification. La base canonique, les transitions transactionnelles, les adaptateurs idempotents et la réconciliation protègent l’exploitation. C’est cette combinaison (et non le modèle seul) qui transforme une automatisation convaincante en véritable système d’inventaire.