Architecture du moteur RFQ : pourquoi les réserves de disponibilité et les instantanés de cancellation importent
Le problème que la plupart des automatisations RFQ manquent
Quand les équipes automatisent les flux RFQ, elles se concentrent généralement sur le front-end : analyser la demande de l'acheteur, chercher dans le catalogue, mettre en correspondance les produits, appliquer les prix. Ce travail est réel, et des outils existent — l'IA extrait les exigences tandis que les règles déterministes gèrent les prix, les approbations et le suivi des SLA, comme GEP le décrit dans son analyse d'automatisation des achats. Le guide de sourcing 2026 d'Ivalua confirme le même schéma : l'IA gère l'extraction et la correspondance, les règles gèrent la gouvernance.
Mais deux problèmes brisent ce modèle en production, et la plupart des automatisations ne les abordent pas :
Dérive de disponibilité. Un devis qui prend trois jours à négocier est un devis contre un inventaire qui peut ne plus exister au prix offert. Si l'agent promet 50 unités à 12,40 $ et que le fournisseur en a 12 quand l'acheteur accepte, le devis n'était jamais livrable. L'agent n'a pas menti — le monde a changé sous ses pieds.
Dérive de politique. Une pénalité d'annulation citée à 30 jours peut différer de la pénalité en vigueur au moment de la réservation. Si le fournisseur resserre les conditions en cours de négociation et que l'agent ne capture pas la politique au moment du devis, l'acheteur conteste la charge plus tard. La contestation est légitime — les termes ont bougé.
L'architecture qui résout les deux est un moteur RFQ avec deux mécanismes : des réserves de disponibilité atomiques qui réservent l'inventaire contraint pour une fenêtre temporelle, et des instantanés de politique d'annulation qui figent les conditions commerciales au moment du devis. Ce ne sont pas des fonctionnalités ajoutées à un outil de devis. Ce sont des engagements structurels dans le modèle de données — des champs, des transitions d'état et des opérations de cycle de vie que le moteur applique.
Le modèle d'entités qui le fait fonctionner
Un moteur RFQ de production a besoin de plus qu'une demande et d'un devis. Le modèle d'entités doit séparer l'intention de l'acheteur de la réponse du fournisseur, et doit suivre l'offre contrainte indépendamment du catalogue. Les entités suivantes forment l'épine dorsale :
Request — l'intention de l'acheteur : contact, titre, articles, adresses de facturation et de livraison, expiration, statut. Une demande passe par une machine d'états définie : initial → in_progress → confirmed → completed, avec modified comme point de réentrée quand les articles changent après l'existence de devis.
Item — le produit du catalogue que l'acheteur veut. Les articles sont génériques ; ils ont un UUID, un type, un mode de prix et des unités de mesure. Le même article correspond à plusieurs articles de fournisseur.
ProviderItem — l'offre spécifique du fournisseur pour cet article, avec un prix de base par unité de mesure. Un article de fournisseur appartient à une entreprise fournisseur et peut avoir plusieurs lots.
ProviderItemBatch — l'unité d'inventaire contraint. Chaque lot a un numéro de lot, une date de production, une date d'expiration, un coût par unité, un indicateur de stock et un indicateur slow_move_item pour l'inventaire à rotation lente. Les lots sont la granularité à laquelle la disponibilité est vérifiée et réservée.
Quote — la réponse tarifée du fournisseur à une demande confirmée. Un devis a un fournisseur, un commercial, un mode et un montant de livraison, une devise, un taux de change et un horodatage de verrouillage FX, un nombre de tours de négociation (auto-calculé) et une machine d'états : initial → in_progress → confirmed → completed ou disapproved.
QuoteItem — une ligne du devis, liant un article et un lot de fournisseur à une quantité, un prix unitaire, un sous-total, une remise et un sous-total final. Critiquement, l'article de devis porte hold_token, hold_expires_at, guardrail_price_per_uom et slow_move_item — les champs qui rendent les réserves de disponibilité et les garde-fous de rentabilité visibles au niveau de la ligne.
CancellationPolicy — les conditions sous lesquelles une réservation peut être annulée, avec des paliers, des échéances et des règles de pénalité, définis par article de fournisseur.
Installment — le calendrier de paiement lié à un devis confirmé, avec priorité, date prévue, montant, ratio et une machine d'états : pending → paid ou cancelled.
Ce n'est pas une table plate. C'est un graphe typé avec des résolveurs imbriqués — une demande résout ses devis, un devis résout ses articles, un article de devis résout son article et lot de fournisseur. Le moteur utilise un chargement par lots basé sur DataLoader pour qu'un devis avec 20 articles ne déclenche pas 20 recherches séquentielles en base de données.
Réserves de disponibilité : réserver la capacité atomiquement
La réserve de disponibilité est le mécanisme qui empêche la dérive de disponibilité. Sans elle, un agent cite contre un instantané d'inventaire qui peut être périmé au moment où l'acheteur décide. Avec elle, l'agent réserve la capacité pour une fenêtre temporelle et le devis est soutenu par une offre réelle, décrémentée.
Le cycle de vie de la réserve a quatre états et des règles de transition strictes :
| État | Signification | Transitions autorisées |
|---|---|---|
held |
Capacité décrémentée, jeton de réserve émis avec un TTL (15 minutes) | confirmed, released, expired |
confirmed |
Réservation engagée, pas de second décrément de capacité | terminal |
released |
Réserve annulée, capacité restaurée (idempotent) | terminal |
expired |
TTL écoulé, capacité restaurée (idempotent) | terminal |
La décision de conception critique est dans la transition confirmed : pas de second décrément de capacité. La réserve a déjà réservé la capacité. Confirmer la réservation convertit la réserve en état confirmé sans toucher au compteur d'inventaire. Cela empêche le double comptage — le bug où une réserve décrémente la capacité, puis une confirmation la décrémente à nouveau, et le système pense avoir moins d'offre qu'il n'en a réellement.
La réserve est atomique. La mutation acquireAvailabilityHold décrémente la capacité disponible et renvoie un hold_token avec un horodatage expires_at. Si la capacité est insuffisante, l'opération échoue sans effets secondaires — pas de réserve partielle, pas d'état sale. Les mutations releaseAvailabilityHold et expireAvailabilityHold restaurent la capacité et sont idempotentes : les appeler deux fois sur le même jeton renvoie le même résultat. Cela importe pour la tolérance aux pannes — si un timeout réseau provoque une rétention, le système ne sur-restaure pas la capacité.
Comment cela se mappe à un flux de devis
Considérez un distributeur qui cite 200 unités d'une pompe industrielle à un fournisseur qui a trois lots avec des dates d'expiration et des coûts différents. L'agent :
- Appelle
check_availabilityavec l'UUID de l'article de fournisseur, la fenêtre de service et la quantité — obtient les lots correspondants et la quantité disponible. - Appelle
acquire_availability_hold— la capacité est décrémentée atomiquement, unhold_tokenavec un TTL de 15 minutes est renvoyé. - L'article de devis porte maintenant
hold_tokenethold_expires_at. L'acheteur voit un devis soutenu par un inventaire réel, pas une supposition de catalogue. - Si l'acheteur accepte dans les 15 minutes, l'agent appelle
confirm_availability_hold— la réserve passe àconfirmed, la capacité est verrouillée, pas de second décrément. - Si l'acheteur refuse ou le TTL expire, l'agent appelle
release_availability_holdou le laisse expirer — la capacité est restaurée, le prochain devis peut l'utiliser.
Le hold_token est un handle explicite. Le modèle le passe en argument, pas comme un état de session caché. C'est le même schéma de handle explicite que le candidat à la publication MCP 2026-07-28 formalise : l'état dont un agent a besoin entre les appels d'outils devient un handle visible et auditable plutôt que des métadonnées de la couche de transport.
Instantanés de cancellation : figer les termes
La dérive de politique d'annulation est le second mode de défaillance. Un fournisseur peut changer les pénalités d'annulation entre le devis et la réservation — une pénalité de 10 % à 30 jours devient 50 % à 7 jours. Si l'agent a cité l'ancienne pénalité et que l'acheteur a accepté, l'acheteur conteste la nouvelle charge. La contestation est correcte.
L'architecture résout cela en instantanéant la politique d'annulation au moment du devis. L'entité CancellationPolicy a des tiers (une structure JSON avec des échéances et des pourcentages de pénalité), un label, une description et un status. Quand l'agent génère un devis, il récupère la politique d'annulation active pour chaque article de fournisseur et attache les termes de la politique au devis. L'UUID de la politique et ses paliers sont figés — même si le fournisseur met à jour la politique plus tard, le devis porte les termes qui étaient en vigueur quand il a été émis.
L'outil search_cancellation_policies filtre par article de fournisseur et statut, pour que l'agent puisse récupérer la politique active pour chaque article de ligne. La politique est alors associée à l'article de devis, pas seulement référencée par une clé étrangère — l'instantané est la politique, pas un pointeur vers un enregistrement mutable.
Ce schéma se généralise au-delà de l'annulation. Les taux de change utilisent la même approche : le QuoteType porte fx_rate et fx_rate_locked_at — le taux de change est verrouillé au moment du devis, pas recherché au moment de la réservation. Si la devise d'affichage de l'acheteur diffère de la devise de devis du fournisseur, le taux verrouillé est le taux que l'acheteur voit, quel que soit le mouvement du marché.
Paliers de prix, recherche de segment et garde-fous
Le prix n'est pas un champ plat. Le moteur résout les paliers de prix par segment client, quantité et article de fournisseur. L'outil get_item_price_tiers prend un email client et une liste d'articles de devis (chacun avec un UUID d'article, un UUID d'article de fournisseur et une quantité), recherche le segment du client via les contacts de segment, et charge par lots tous les paliers de prix correspondants en une seule requête. Les paliers sont filtrés par seuils de quantité au niveau base de données — seuls les paliers où quantity_greater_then et quantity_less_then encadrent la quantité demandée sont renvoyés.
Les invites de remise fonctionnent de la même manière, avec un système de portée hiérarchique : GLOBAL (s'applique à tous les devis), SEGMENT (s'applique à un segment client), ITEM (s'applique à un article spécifique) et PROVIDER_ITEM (s'applique à l'offre d'un fournisseur spécifique pour un article). L'outil get_discount_prompts charge depuis les quatre portées, dédupliquee et renvoie l'ensemble combiné avec les conditions et règles. Cela signifie qu'un devis peut appliquer une remise de volume (portée segment), une promotion saisonnière (portée article) et une liquidation spécifique fournisseur (portée article de fournisseur) simultanément — chacune avec sa propre priorité.
Le champ guardrail_price_per_uom sur l'article de devis est le garde-fou de rentabilité — le prix minimum acceptable pour l'article, chargé depuis le lot d'article de fournisseur. Si l'agent applique une remise qui fait tomber le prix sous le garde-fou, l'opération n'échoue pas (l'agent peut avoir une raison légitime), mais l'indicateur est visible dans la réponse de l'article de devis. L'indicateur slow_move_item marque l'inventaire en stock depuis trop longtemps — l'agent peut l'utiliser pour appliquer des remises plus profondes, mais le garde-fou empêche la tarification non rentable.
Les machines d'états qui empêchent les opérations invalides
Un moteur RFQ sans application d'état est une feuille de calcul. Le moteur applique quatre machines d'états, chacune avec des règles de transition explicites et des gardes d'opération :
Request : initial → in_progress → confirmed → completed, avec modified comme point de réentrée. Le RequestOperationGuard applique que les devis ne peuvent être créés qu'à partir de demandes confirmed, et que les modifications d'articles ne sont autorisées que dans les états initial, in_progress ou modified.
Quote : initial → in_progress → confirmed → completed ou disapproved. Le QuoteOperationGuard applique que les modifications d'articles ne sont autorisées que dans initial ou in_progress, et que les échéances ne peuvent être créées qu'à partir de devis confirmed.
Réserve de disponibilité : held → confirmed / released / expired. Les trois états cibles sont terminaux.
Installment : pending → paid ou cancelled. Les deux états cibles sont terminaux.
Quand les articles d'une demande confirmée changent et que des devis existent déjà, la demande passe à modified et tous les devis existants sont automatiquement rejetés — l'acheteur doit obtenir de nouveaux devis contre les articles mis à jour. Cela empêche les devis périmés d'être acceptés après que la demande qui les a générés ait changé.
Quand toutes les échéances non annulées d'un devis sont payées, le devis passe automatiquement à completed. Quand au moins un devis d'une demande est complété, la demande passe automatiquement à completed. Les machines d'états ne sont pas consultatives — le moteur valide chaque transition et lève une erreur VALIDATION_FAILED avec le statut actuel et les transitions autorisées si l'opération est invalide.
Le schéma de module : 38 outils, 11 mixins de domaine
L'implémentation de référence de cette architecture est un module MCP unique — mcp_hospirfq_processor — avec 38 outils enregistrés composés de 11 mixins de domaine : Request, Item, Availability, Quote, Pricing, Installment, Bundle, Cancellation, File, Segment et Catalog. Les mixins partagent une classe de base GraphQLBackedProcessor qui fournit le client GraphQL, le logger et les paramètres. Chaque mixin contribue ses propres méthodes d'outil et n'accède qu'à self.logger, self.setting et self._execute_graphql_query.
Les outils sont enregistrés via un bloc de configuration (MCP_CONFIGURATION) qui déclare le nom de chaque outil, la description, le schéma d'entrée (JSON Schema) et le lien de module (nom de classe, nom de fonction, type de retour). Le runtime MCP charge cette configuration au démarrage et distribue les appels d'outils aux méthodes de mixin correspondantes. Le décorateur @handle_errors enveloppe chaque appel d'outil avec une gestion d'erreur structurée, produisant des réponses d'erreur typées avec des codes comme GRAPHQL_QUERY_FAILED, VALIDATION_FAILED, HOLD_NOT_FOUND, HOLD_ALREADY_EXPIRED, AVAILABILITY_INSUFFICIENT et PRICING_MODE_UNSUPPORTED.
Les 38 outils couvrent le cycle de vie RFQ complet :
| Domaine | Outils | Opérations clés |
|---|---|---|
| Gestion des demandes | 8 | Soumettre, mettre à jour, obtenir, rechercher, ajouter/supprimer des articles, assigner/supprimer des articles de fournisseur |
| Gestion des articles | 3 | Rechercher des articles, obtenir un article, obtenir des articles de fournisseur avec infos de lot |
| Gestion des devis | 4 | Mettre à jour un devis, obtenir un devis, rechercher des devis, mettre à jour un article de devis |
| Prix | 3 | Obtenir les paliers de prix, obtenir les invites de remise, calculer les prix du devis |
| Échéances | 4 | Créer, mettre à jour, créer un calendrier, obtenir les échéances |
| Disponibilité | 5 | Vérifier, acquérir une réserve, libérer, confirmer, expirer |
| Bundles | 3 | Rechercher des bundles, obtenir un bundle, rechercher des composants |
| Annulation | 2 | Obtenir une politique, rechercher des politiques |
| Catalogue | 1 | Interroger le catalogue (recherche dans le graphe de connaissances) |
| Segment | 1 | Obtenir les contacts de segment |
| Fichier | 2 | Télécharger, obtenir des fichiers |
| Flux | 2 | Confirmer la demande + créer des devis, confirmer le devis + créer des échéances |
Les deux outils de flux — confirm_request_and_create_quotes et confirm_quote_and_create_installments — sont des opérations de convenience qui composent plusieurs outils atomiques en un seul appel. Le premier confirme une demande et crée des devis pour les fournisseurs sélectionnés en une opération. Le second confirme un devis et crée le plan d'échéances en une opération. Celles-ci réduisent le nombre d'allers-retours dont un agent a besoin pour les flux multi-étapes les plus courants.
L'outil inquire_catalog est le pont vers le graphe de connaissances. Il prend une requête en langage naturel, un espace de noms optionnel (hotel, flight, activity) et une limite, et renvoie des résultats classés depuis le moteur de graphe de connaissances. C'est ainsi que l'agent découvre les produits avant de deviser — pas par des requêtes SQL contre une table de produits, mais par une recherche sémantique à travers un graphe de catalogue qui connaît les substituts, la compatibilité et la disponibilité.
Pourquoi cette architecture se généralise
L'implémentation de référence a été construite pour le voyage et l'hôtellerie — inventaire daté, règles d'occupation, tarification par passager, bundles, dépôts, conditions d'annulation. C'est le test de résistance. Si le moteur peut deviser et réserver une chambre d'hôtel pour des dates spécifiques, réserver la disponibilité avec un TTL, instantaner la politique d'annulation, verrouiller le taux de change, appliquer des paliers de prix spécifiques au segment et livrer à un plan d'échéances, il peut supporter tout flux RFQ B2B avec fournisseurs, acheteurs, inventaire contraint et conditions commerciales.
La généralisation est structurelle :
- Distribution et gros : remplacer les chambres d'hôtel par le stock d'entrepôt, les articles de fournisseur par les SKU fournisseur, les lots par les numéros de lot et dates d'expiration. La réserve de disponibilité réserve le stock ; l'instantané d'annulation préserve les conditions de retour.
- Approvisionnement : remplacer les forfaits voyage par des pièces et matériaux. Les paliers de prix basés sur les segments appliquent les taux contractuels négociés ; le prix garde-fou empêche l'approvisionnement non rentable.
- Manufacturing : remplacer les fenêtres de service par des créneaux de production. La réserve de disponibilité réserve la capacité sur une ligne de production ; la politique d'annulation préserve la pénalité de reprogrammation.
- Services sur le terrain : remplacer les dates d'hôtel par des fenêtres de rendez-vous. La réserve de disponibilité réserve le créneau d'un technicien ; le plan d'échéances structure le calendrier de paiement.
Le modèle d'entités — Request, Item, ProviderItem, ProviderItemBatch, Quote, QuoteItem, CancellationPolicy, Installment — est agnostique à l'industrie. Les mixins de domaine sont composables : un déploiement d'approvisionnement pourrait supprimer le mixin Bundle et garder le reste. Un déploiement de manufacturing pourrait ajouter un mixin ProductionSlot aux côtés d'Availability. Le schéma de module, les machines d'états et l'approche de handle explicite restent les mêmes.
Un distributeur faisant tourner NetSuite, BigCommerce et trois catalogues de fournisseurs obtient un agent qui reçoit un RFQ par email ou portail, résout les produits et substituts contre le graphe de catalogue, applique les prix par segment client, réserve le stock avec une expiration, instantané les conditions d'annulation, verrouille le taux de change et réécrit le devis accepté dans NetSuite — chaque appel d'outil étant journalisé et chaque transition d'état validée. Cette construction est la Phase 2-3 de la méthode en quatre étapes et est généralement live en 5 à 8 semaines.
Demandez une construction délimitée. Découverte d'une semaine. Vous obtenez un inventaire des systèmes, une cartographie des flux et un périmètre fixe — que vous construisiez avec nous ou non.
Vous voulez cela construit pour vos systèmes ?
Chaque document ici provient d'un travail réel en production. Si vous avez un système cible et un flux en tête, nous pouvons cadrer une construction en une semaine.
Demander un projet cadréDécouverte d'une semaine. Vous obtenez un inventaire des systèmes, une cartographie des flux et un périmètre fixe — que vous construisiez avec nous ou non.