J'ai construit le moteur de prévision de SaleCast — voici les 3 problèmes que personne n'anticipe
Tout le monde croit que le forecasting, c'est « entraîner un modèle et lire ses prévisions ». En production, trois problèmes surgissent que personne n'anticipe : aucun modèle unique ne marche, un bon modèle se met à mentir en silence, et le bon prix n'est jamais dans l'intuition. Voici comment je les ai résolus dans le moteur de SaleCast — en .NET, avec une boutique fictive suivie de bout en bout.
Cet article décortique le projetSaleCastDemandez à n'importe qui ce qu'est le forecasting et vous obtiendrez : « on entraîne un modèle sur l'historique, et il prédit la suite ». C'est vrai en cours. En production, c'est le début des ennuis.
J'ai construit le moteur de prévision de SaleCast, et j'ai rencontré les trois problèmes que ce résumé escamote. Aucun n'est un problème de data science pure — ce sont des problèmes d'ingénierie, ceux qui décident si un modèle sert vraiment à quelque chose une fois branché sur un vrai catalogue. Les voici.
En bref
Trois problèmes que le forecasting « de cours » ignore, et leur réponse dans le moteur de SaleCast : (1) aucun algorithme unique ne prédit bien tous les produits → 12 algorithmes concourent par produit ; (2) un modèle qui devient mauvais ne lève aucune erreur → un détecteur de dérive surveille ses résidus ; (3) le bon prix n'est pas une intuition → une régression d'élasticité le mesure, toujours avec son incertitude. Ce que ça montre : du ML en production en .NET (sélection automatique, observabilité, économétrie appliquée), pas un modèle entraîné une fois.
Le contexte (et l'honnêteté qui va avec)
SaleCast est un SaaS e-commerce multi-canaux développé pour un client direct (statut : privé, en cours de développement). C'est un produit co-développé : ma contribution couvre les connecteurs de données (l'ingestion qui aspire les ventes de chaque canal) et le moteur de prévision ; un second développeur a construit le reste de la plateforme — réconciliation des sources, hosts, UI.
Le moteur n'a pas encore de résultats client confirmés en production. Tous les chiffres de cet article sont illustratifs, calculés sur des jeux de données de test — pas des gains client réels. Je le précise une fois ; ça vaut pour toutes les sorties affichées plus bas.
Pour rendre l'ensemble concret, on suit une boutique fictive mais crédible tout du long — Atelier Nord : ~5 000 références vendues sur 8 canaux (sa boutique Shopify, Amazon, eBay, Google Shopping…). Le fil rouge : brancher les canaux → obtenir une série propre par produit → en tirer des décisions par produit.
Le travail invisible : des données brutes à une série propre
Avant de prévoir quoi que ce soit, il faut des données propres. C'est la première moitié de
mon périmètre : les connecteurs. Chaque canal expose ses ventes différemment (REST paginé,
webhooks, exports CSV, quotas, formats de dates et devises divergents). Un connecteur a un seul
contrat : transformer ce chaos en évènements de vente normalisés, puis les agréger en
une série quotidienne par produit — les jours sans vente devenant des 0 explicites, pas
des trous.
Ce détail des zéros explicites n'est pas cosmétique : c'est lui qui permet, juste après, de distinguer un produit « vendu peu mais régulièrement » d'un produit « vendu par à-coups ». Et comme on va le voir, toute la suite en dépend. La qualité de la prévision se joue ici, dans le travail d'ingestion peu glamour — bien avant le moindre modèle.
Problème 1 — Aucun modèle unique ne marche
Prenons trois produits typiques du catalogue d'Atelier Nord :
- T-shirt Marin XL noir : vendu tous les jours, courbe lisse, saisonnalité été/hiver.
- Guirlande LED 50 ampoules : 90 % des ventes en novembre-décembre, le reste à zéro.
- Adaptateur secteur USB-A : déclin lent, 2 ventes par semaine, des semaines à zéro.
Trois courbes radicalement différentes. Aucun algorithme seul ne les prévoit toutes correctement. Holt-Winters est bon pour le t-shirt et catastrophique pour la guirlande (les zéros le cassent). Croston est conçu pour le déclin intermittent et ridicule sur le saisonnier. LightGBM est puissant avec des centaines de jours d'historique, inutile sur 30 jours bruts.
La réponse industrielle tient en une phrase : classer chaque produit par pattern de demande, faire concourir les candidats pertinents, garder le meilleur — automatiquement.
Classer avant de choisir
Avant la compétition, on calcule deux statistiques toutes simples sur la série : l'intervalle moyen entre deux ventes (ADI) et la variabilité des quantités vendues (CV², mesuré sur les jours non nuls pour ne pas être faussé par les zéros). Le croisement des deux donne la classification Syntetos-Boylan — quatre familles : lisse, erratique, intermittent, sporadique. Chaque famille oriente vers des candidats différents.
Faire concourir — sans tout faire concourir
Au total, 12 candidats : 8 méthodes statistiques codées en C# (Naive, SES, Theta, Croston, TSB, Holt-Winters, Seasonal-Naive, MSTL), LightGBM via ML.NET, deux foundation models time-series (TiRex et Chronos) et un Ensemble qui combine les meilleurs.
Ce qui rend la compétition possible, c'est qu'ils respectent tous le même contrat. Le moteur ne sait rien du « comment » d'un algorithme : il les fait juste prévoir la même série et compare.
public interface IForecastAlgorithm
{
// Cet algo est-il applicable à cette série ? (Holt-Winters exige de
// l'historique saisonnier ; Croston veut de l'intermittence…)
bool CanHandle(ProductSalesHistory history, ForecastProfile profile);
// Produit une prévision sur l'horizon demandé + ses intervalles.
ForecastResult Forecast(ProductSalesHistory history, ForecastRequest request);
}
Faire concourir 12 algos à fond sur des milliers de produits coûterait une fortune en calcul. La parade tient en deux étages : on présélectionne d'abord par pattern (un produit intermittent n'envoie pas Holt-Winters dans la course), puis on fait un screening rapide sur quelques points pour tout le monde, et on ne paie le back-test complet que sur les 3 finalistes. D'après les mesures internes du module, ~60 % d'évaluations en moins, sans perdre en qualité de sélection.
L'idée à retenir
On ne demande pas à un humain « Holt-Winters ou Croston pour ce produit ? » sur des milliers de références — ça n'a pas de sens. On classe, on screene vite, et on ne paie le back-test complet que sur les finalistes. La compétition par produit devient tenable à l'échelle.
La frontière .NET ↔ Python, assumée
Détail qui résume bien ma façon de travailler : le ML « classique » reste 100 % .NET — les
8 statistiques en C#, LightGBM via Microsoft.ML.LightGbm, et même le foundation model TiRex
(architecture xLSTM, ~35M paramètres) tourne en .NET via un export ONNX et OnnxRuntime,
sans runtime Python. Seul Chronos (Amazon), qui n'a pas d'export ONNX propre, passe par un
petit sidecar Python en HTTP — et s'il est indisponible, le client renvoie simplement
null : la compétition continue avec les 11 autres. Aucun produit ne reste sans prévision
parce qu'un sidecar a planté.
Le tout-Python est fragile à exploiter en prod ; le tout-.NET est pauvre sur les foundation models récents. Le mix .NET pur + un seul sidecar HTTP pour le strict nécessaire est le compromis que j'ai retenu.
À l'arrivée, chaque produit a une décision tracée : quel algo a gagné, avec quel score,
par back-test réel ou non. Sur les 3 produits d'Atelier Nord, ça donne HoltWinters pour le
t-shirt saisonnier, Ensemble pour la guirlande à intermittence extrême, Croston pour
l'adaptateur en déclin. Si un produit gagne avec le baseline Naive, c'est un signal d'alerte
(série trop courte, données corrompues), pas un succès.
Problème 2 — Un bon modèle se met à mentir, en silence
Le modèle est choisi, il tourne, tout va bien. Pendant des semaines. Puis le marché bouge — une rupture d'appro, une mode qui démarre, un concurrent qui casse ses prix — et le modèle, entraîné sur le monde d'avant, se trompe de plus en plus. Il ne crash pas. Il ne lève aucune erreur. Les chiffres ont l'air plausibles. Et le réassort calculé sur ses prévisions devient faux, sans qu'on sache quand le modèle a décroché.
Un serveur qui crash, on le voit en 30 secondes. Un modèle qui ment, rien ne le signale. C'est le bug le plus discret du ML en production — et celui que résout le détecteur de dérive.
L'astuce : on ne surveille pas le modèle, on surveille ses résidus — l'écart entre prévu et réel. Tant qu'ils oscillent autour de zéro, le modèle colle. S'ils se mettent à dériver durablement d'un côté, il sur- ou sous-estime systématiquement. Deux tests classiques tournent en parallèle sur ce flux et déclenchent si l'un des deux signale une dérive :
var driftDetected = pageHinkley.Detected || adwinLike.Detected;
var severity = ComputeSeverity(pageHinkley, adwinLike); // High si les deux
Page-Hinkley (1954, un algorithme de 70 ans qui tient en un seul passage) attrape les dérives lentes et continues. Un second test, inspiré d'ADWIN, coupe la série de résidus en deux fenêtres (anciens 60 % / récents 40 %) et compare leur erreur : si la fenêtre récente est nettement pire, dérive. Il attrape les dégradations franches. L'un couvre ce que l'autre rate.
Quand une dérive est détectée, le produit repart automatiquement en compétition complète (les 12 candidats). Si un autre algorithme bat l'ancien, il prend sa place ; sinon on garde, mais on note l'instabilité. Aucun humain dans la boucle.
Honnêteté de mise en œuvre
Mon second test est inspiré d'ADWIN, ce n'est pas l'implémentation complète du papier (pas de fenêtre adaptative ni de garantie statistique formelle) — juste une comparaison de fenêtres robuste et bon marché. Le dire évite de survendre, et c'est plus utile : un détecteur simple qui tourne vaut mieux qu'un détecteur sophistiqué qui n'existe que sur le papier.
La leçon : un système prédictif qui ne dit jamais « je suis perdu » est dangereux. Et une dérive détectée n'est pas une panique — c'est un signal d'observabilité qui dit « va vérifier cette catégorie ». La plupart du temps c'est une promo ; parfois un vrai changement de marché. Utile dans les deux cas.
Problème 3 — Le bon prix n'est pas dans l'intuition
Atelier Nord vend une Box Cadeau à 30 €, ~220 par mois. La question du commerçant : « si je passe à 35 €, j'en perds combien ? »
Le calcul naïf — 35 € × 220 — suppose que la quantité ne bouge pas. Faux : si la hausse fait tomber les ventes à 150, le chiffre d'affaires baisse. Toute la question est : de combien la quantité va-t-elle reculer ? La réponse n'est pas dans la tête du commerçant. Elle est dans son historique de ventes — et ça se mesure.
L'outil tient en une ligne de microéconomie : dans un modèle log-log, la pente de la
régression de log(quantité) sur log(prix) est directement l'élasticité. Une pente de
-1,8 veut dire qu'une hausse de prix de 10 % fait reculer la demande de ~18 % (produit
élastique, à éviter) ; une pente de -0,4, seulement ~4 % (inélastique, hausse probablement
rentable). Le modèle tient en quelques lignes de C#, sans réseau de neurones ni GPU.
La valeur n'est pas dans le modèle — elle est dans les garde-fous. Trois pièges faussent tout, et le code refuse de répondre plutôt que de mentir :
- Pas assez d'historique → on renvoie « impossible à estimer », pas un chiffre inventé.
- Le prix n'a jamais bougé → rien à mesurer, on s'abstient.
- La boutique gagne en notoriété (ventes ET prix montent avec le temps, sans lien causal) → on retire d'abord la tendance temporelle (detrending) avant de mesurer l'effet du prix.
Et surtout : on n'affiche jamais l'élasticité seule. Toujours avec son R², son t-stat et le
nombre d'observations. Un β = -1,4 calé sur 6 points avec un R² de 0,12, c'est du bruit
déguisé en signal.
La phrase à ne JAMAIS dire à un client
« Vous pouvez augmenter de 10 %, vous perdrez 5 % de ventes. » Ça sonne professionnel, c'est faux par construction : ça présente une estimation incertaine comme une certitude. La bonne version : « Sur ce produit, l'élasticité estimée est de -0,41 (R² = 0,18, sur 284 jours). Signal modéré : +10 % de prix ≈ -4 % de quantité, tant que rien d'autre ne change. Je suggère de tester 30 jours, puis de re-mesurer. » Le chiffre est étayé, l'incertitude est nommée, un plan B est posé.
Ce que ces trois problèmes ont en commun
Aucun des trois n'est un problème de modèle. Le meilleur algorithme de prévision ne sert à rien s'il est choisi à la main produit par produit, s'il dérive sans qu'on le voie, ou s'il crache un chiffre sans dire à quel point il en est sûr. Ce qui transforme un POC en système qui tient en production, c'est :
- L'automatisation du choix — classer, faire concourir, garder le meilleur, le tracer.
- L'observabilité — surveiller les résidus, détecter la dérive, se ré-aligner seul.
- L'honnêteté sur l'incertitude — un chiffre n'a de valeur qu'avec son intervalle de confiance.
Et un fil rouge discret : tout ça tient en .NET, avec un seul sidecar Python pour le strict nécessaire. ML.NET + ONNX Runtime couvrent l'essentiel ; l'écosystème .NET ML est viable en production, à condition d'assumer une frontière nette quand il faut sortir du langage.
C'est exactement le genre de système que j'aime construire : du ML qui rend des comptes, pas une boîte noire.
Stack & code
- Solution Forecasting = sous-projets
Core,Api,CLI,Desktop(host Photino),Web,Web.Server - 8 méthodes statistiques en C# + LightGBM (
Microsoft.ML.LightGbm) + 2 foundation models (TiRex en ONNX Runtime, Chronos en sidecar Python HTTP) + Ensemble softmax - Sélection :
DemandClassifier(SBC) →ModelRouter→AlgorithmCompetition(screening + full back-test) - Dérive :
DriftDetector— Page-Hinkley + détecteur ADWIN-inspiré, C# pur, sans dépendance externe - Pricing :
PriceElasticityEstimator— régression log-log + detrending + t-stat, OLS maison, C# pur - ML.NET 5.0.0 + Microsoft.ML.OnnxRuntime 1.24 · Hangfire (jobs de fond) · Blazor + LumexUI (studio de visualisation)
Pour le cadrage produit complet (réconciliation multi-canaux, connecteurs, hosts), voir la fiche projet SaleCast. Et sur les choix d'architecture temps réel qui reviennent dans mes projets (dont l'approche ActualLab Fusion reprise à l'échelle sur OneRP) : 3 décisions d'archi qui ont sauvé mes projets.
Florian Sola
Développeur Senior C#/.NET/GenAI — Lead Tech & Architecte logiciel · 9 ans d'expérience
La suite logique
Ce sujet ressemble à ce que vous devez livrer ? Parlons-en.
Je prends des sujets techniques critiques — du cadrage à la production, sans dette ni dépendance après le transfert. Le plus rapide pour voir si ça colle : 30 minutes en visio.
Réponse sous 24 h — souvent bien avant.