Intelligence — Prediction Engine Roadmap (le "+8 ans d'avance")
Spec du prochain PR. Objectif : passer d'un moteur qui observe + explique (état actuel, post-PR §21) à un moteur qui prédit. C'est la bascule qui rend toute comparaison avec un "tool" (les outils spy du marché…
Spec du prochain PR. Objectif : passer d'un moteur qui observe + explique (état actuel, post-PR §21) à un moteur qui prédit. C'est la bascule qui rend toute comparaison avec un "tool" (les outils spy du marché, SimilarWeb…) absurde : eux montrent le passé, nous anticipons le futur, et nous prouvons notre avance par backtesting.
Statut : Phases 1 + 2 LIVRÉES. Phase 1 = moteur prédiction (lots 1-5, 8, backtest). Phase 2 = omniscience marché (lots 6, 7, 9, 11, 13). Reste : surfaces UI dédiées (lot 10) + exécution bulk crawl / ClickHouse provisioning (ops). Pré-requis : pipeline canonical (sections et champs :
pnpm intel:coverage) + graph + SEO + ads multi-network + teardown + sentiment (cf.intelligence-pipeline.md§21).Livré Phase 2 :
MarketClusterPrisma + modulemarket/(agrégation niche, emerging-niche detection) + cronmarket-aggregate;supplier/(cohorte fournisseur via handles + stems d'images partagés, score commodité, likely-dropship) + signal graphproduct_image_stem;creative-trends/(getWinningAngles, hooks se propageant inter-marques) ; driver review Trustpilot réel (page publique, alimente velocity + sentiment) ; proactif cronprediction-fanout(Faye pousse breakout/opportunity des stores watchés dans PlatformActivity) ; 4 tools agents (getSupplierIntel,getMarketTrends,findEmergingNiches,getWinningAngles) + 3 tools MCP + 3 routes API (/markets,/winning-angles,/supplier/[domain]). Typecheck 0 erreur, 383/383 tests verts.Livré ce PR : section canonique
prediction(13e) ; modèlesStoreMetricDaily+PredictionAccuracy; moteurprediction/(series-math, breakout/lifecycle, saturation, Holt forecast, opportunity scorer) ; rollup + backfill depuis snapshots ; backtest harness + cronprediction-backtest; câblage orchestrateur (pass 4 post-graph) ; tools agentspredictStoreTrajectory+findOpportunities+ tool MCPpredictStoreTrajectory; surface détail. 12 tests prédicteurs + 6 graph + 76 intelligence verts, typecheck 0 erreur.
0. Bilan — où on en est (honnête)
Ce qui est livré et solide ✅
| Couche | État |
|---|---|
Record canonical L1/L2/L4 (sections : pnpm intel:coverage) | Production-ready. Reconciler + freshness + plausibility + backfill forward-compat. |
Probes (catalog, theme, apps, pixels, analytics, reviews, social, traffic, commerce, seo, …) Le compte qui fait autorité est Object.keys(PROBES).length (probes/index.ts) et la taille de PROBE_SETS.full (shopify-deep-scan.ts). ads est un alias enregistré vers meta_ad_library_fetcher, donc compter les fichiers du dossier ne donne jamais ce nombre. Les « 31 enregistrés / 30 dans full » tenus à la main ici étaient périmés. | |
| Ads — Meta + TikTok + Google + Pinterest | Multi-network (searchAllNetworks + ads.networks_detail). |
| Reviews + sentiment IA (objections/motivations/USP cachées) | Judge.me + Loox riches ; sentiment Haiku sur full scans. |
| SEO — on-page + off-page (Ahrefs/Semrush/DataForSEO) | On-page gratuit, off-page fail-soft. |
| Store Graph + Clone Intelligence | StoreSignalIndex + scorer testé. same_owner / network / clone. |
| Teardown stratégique (Sonnet) | Pourquoi ça scale, faiblesses, opportunités, angles/marchés/offres. |
| Similar stores (embeddings Upstash) | Vector lookalikes. |
| Calibration OAuth (ground truth) | Le moat irréplicable. Plombé end-to-end. |
| Anomalies (8 types) | ad_burst, restock, review_burst, theme_change, … |
| Snapshots time-series | AdActivitySnapshot, CatalogDelta, IntelligencePriceObservation, IntelligencePanelEvent. |
| Refresh tiers HOT/WARM/COLD + bulk-indexer | Crons + infra prêts. |
Ce qui est partiel (à durcir : détaillé en §6) ⚠️
- Calibration : plombée mais les multiplicateurs ne sont pas encore flippés (
MIN_SAMPLES_FOR_ADJUST=30jamais atteint). → les estimations restent en prior heuristique. - Reviews riches : seuls Judge.me + Loox ont des drivers complets ; Yotpo/Stamped/Okendo/Trustpilot/Reviews.io sont détectés mais pixel-only.
- Creative clustering : intra-store (un store à la fois), pas cross-store.
- Bulk indexing : infra prête, crawl jamais lancé → couverture encore on-demand.
- Time-series : sur Postgres (abstraction ClickHouse en stub).
- Inventory velocity : ~30-40% des stores (dépend de l'exposition publique du stock).
Ce qui manque totalement (le cœur de ce PR) ❌
Tout est rétrospectif. On sait dire "ce store fait ~X€/mois, voici son stack, ses concurrents, ses avis". On ne sait pas dire :
- "ce produit va exploser dans 21 jours" (avant saturation)
- "ce store va plafonner d'ici 6 semaines"
- "cette niche émerge, fenêtre d'entrée encore ouverte ~60j"
- "ce hook créatif se propage sur le marché, copie-le maintenant"
- "ces 47 stores sourcent le même fournisseur, marge ~68%"
C'est exactement ce que personne ne fait, et c'est ce qui donne +8 ans d'avance.
1. Thèse — pourquoi la prédiction nous rend inrivalables
Les "tools" vendent un rétroviseur (data du passé scrapée). Nous vendons un radar + GPS : où va le marché, et quoi faire maintenant.
Le moat n'est pas une feature, c'est un flywheel composé :
Plus de stores OAuth connectés
→ plus de ground truth L1
→ calibration + backtesting des prédictions
→ prédictions vérifiées (precision/recall publiés en interne)
→ produit qui "voit juste" → plus d'utilisateurs
→ plus de stores connectés ↺
Un concurrent qui veut nous rattraper doit : (a) avoir un produit OAuth vers le store Shopify du marchand en masse, l'outil de référence et SimilarWeb n'en ont pas ; l'OAuth 2.1 que l'outil de référence expose depuis 2026 authentifie vers leur workspace, pas vers le store du marchand, (b) accumuler des années de time-series avec ground truth pour calibrer, (c) reconstruire l'orchestration multi-agents qui agit sur la prédiction. Les trois ensemble = des années. À 1 000 stores connectés, l'écart est définitif.
Trois principes non-négociables (cohérents avec le repo) :
- Toute prédiction porte un intervalle de confiance + un horizon. Jamais un chiffre nu. (Honnêteté = signal différenciant, cf. §7 pipeline.)
- Toute prédiction est backtestée. On ne shippe pas un prédicteur sans sa courbe d'accuracy historique.
- L'IA agit. Une prédiction sans CTA agent ("@Maya, scale ce produit") est à moitié faite.
2. Le Prediction Engine (centerpiece)
Nouveau dossier src/services/algorithms/intelligence/prediction/. Pur TS, pas de service ML externe en v1 (modèles statistiques + heuristiques calibrées ; ML lourd en v2 si le backtesting le justifie).
2.1 Feature store time-series unifié
Aujourd'hui les snapshots sont éclatés (AdActivitySnapshot, CatalogDelta, …). On ajoute une vue d'agrégation par store × jour, la substrat d'entraînement.
Prisma — StoreMetricDaily (rollup quotidien, append-only) :
model StoreMetricDaily {
id String @id @default(cuid())
domain String @db.VarChar(255)
day DateTime @db.Date
activeCreatives Int?
adSpendEur Float?
reviewVelocity Float? // reviews/jour
monthlyVisits Int?
socialFollowers Int?
productCount Int?
priceIndex Float? // médiane prix normalisée
discountDepth Float? // 0-1
newProducts7d Int?
marketsCount Int?
createdAt DateTime @default(now())
@@unique([domain, day])
@@index([domain, day])
}
- Rempli par un cron
prediction-rollup(daily) qui agrège les snapshots existants. Rétro-rempli depuis l'historique disponible. - C'est la table que les prédicteurs lisent (séries propres, pas de JSONB walking).
2.2 Lifecycle / breakout predictor : prediction/breakout.ts
Classifie chaque produit/store dans un stade de cycle de vie et calcule une probabilité de breakout à partir d'indicateurs avancés (dérivées, pas niveaux) :
| Signal avancé | Source | Pourquoi c'est précoce |
|---|---|---|
| Accélération du nb de créatives (2e dérivée > 0) | AdActivitySnapshot | un store qui multiplie ses créas teste un gagnant |
| Hausse du taux de lancement de créas | creative timestamps | itération = signal de scaling |
| Inflexion review velocity | reviews recent_count_30d série | ventes réelles qui accélèrent |
| Momentum social (followers/mentions ↑) | social-metrics série | demande amont |
| Apparition de nouveaux marchés/pays en ads | ads countries delta | expansion géo = confiance |
| Prix stable + discount faible | commerce série | early stage (pas encore en bataille de prix) |
Sortie → section canonique prediction (nouvelle) :
stage: "emerging" | "early_scaling" | "scaling" | "peak" | "saturating" | "declining" | "unknown"
breakout_probability: ConfidenceInterval // 0-1
predicted_window_days: number // horizon estimé
drivers: string[] // signaux qui portent la prédiction
2.3 Saturation / decline predictor : prediction/saturation.ts
Indicateurs retardés inversés (fatigue) :
- ralentissement du refresh créatif (churn créas ↓ alors que spend plat)
- décroissance review velocity
- hausse de la profondeur de discount (pression marge = fin de cycle)
- plateau/décroissance trafic
→
saturation_risk: ConfidenceInterval+predicted_decline_window_days.
2.4 Revenue/traffic forecaster : prediction/forecast.ts
Prévision time-series (Holt-Winters / EWMA amorti en v1 ; gradient-boosting en v2) sur StoreMetricDaily, avec bandes de confiance. Pour les stores OAuth : calibré contre le revenu réel → le forecaster est validé, pas inventé. Sortie : revenue_forecast_30d/90d (CI), traffic_forecast (CI).
2.5 Opportunity scorer : prediction/opportunity.ts (gap E "définitif")
Le Graal : forte demande × faible concurrence.
opportunity_score = f(demand_momentum, inverse_competition_density, margin_proxy, trend_freshness)
demand_momentum: momentum produit/niche (search + social + ad spend agrégé marché, §3).competition_density: nb de stores du graph vendant ce produit/niche + saturation ad.margin_proxy: (prix de vente médian) ÷ (prix fournisseur estimé via supplier intel §3.3). → répond enfin à "montre-moi les produits qui explosent mais avec faible concurrence".
2.6 Câblage
- Les prédicteurs tournent dans un cron
prediction-tick(daily/6h selon tier) après le rollup, lisentStoreMetricDaily+ le record, et émettent la sectionprediction(reconciliée, L4, avec CI obligatoire : déjà supporté par le contrat). - Nouvelle section canonique
prediction: suivre la mécanique §21/Phase A (sections.ts + template.ts + emission.ts + schema.ts optional + plausibility + freshness kindprediction+ barrel). Backfill forward-compat déjà en place → zéro migration.
3. Cerveau agrégé marché (cross-store)
Aujourd'hui tout est par-store. La vraie puissance émerge à l'agrégat.
3.1 Niche/market graph : prediction/market.ts + MarketCluster Prisma
Agrège le store graph + embeddings en clusters de marché. Par cluster : revenu agrégé, nb de stores, taux d'entrée (nouveaux stores/mois), spend ad agrégé, momentum. → détection de niches émergentes avant saturation + "difficulté d'entrée" par niche.
3.2 Cross-store creative trends : étendre creative-clusterer
Clustering créatif inter-stores (embeddings vision déjà calculés par creative-vision-analyzer). Détecte un hook/angle qui se propage sur le marché → "winning angle radar" : copie l'angle avant qu'il sature. CreativeTrend Prisma (cluster cross-store, vélocité d'adoption, stores l'utilisant).
3.3 Supplier intelligence : prediction/supplier.ts (gap "fournisseur")
Perceptual-hash des images produits (catalog.top_products[].image_url) → détecte produits/fournisseurs partagés entre stores. Sortie : "ce produit est vendu par N stores, source probable = X (AliExpress/CJ/POD fingerprint), prix de vente médian Y, marge estimée Z". Étend le graph avec des edges shared_supplier + nourrit margin_proxy (§2.5). C'est le "K. Store Clone Intelligence" poussé au niveau supply-chain.
4. Calibration active + backtesting (la crédibilité = l'arme)
Sans ça, "prédiction" = "opinion". Avec ça, c'est prouvé.
4.1 Activer la calibration
- Recruter/connecter assez de stores OAuth pour franchir
MIN_SAMPLES_FOR_ADJUST(30) par champ, flipper les multiplicateurs, resserrer les CI. (Dépend en partie du beta program : déjà scaffolded.) - Étendre la calibration aux nouveaux champs de prédiction (breakout réalisé ? forecast vs réel ?).
4.2 Backtesting harness : prediction/backtest.ts + cron weekly
- Rejoue les snapshots historiques : génère la prédiction as-of T, compare à l'actuel T+horizon.
- Calcule precision/recall (breakout, saturation), MAPE (forecast) par prédicteur.
- Persiste
PredictionAccuracy(par modèle, par horizon) → dashboard interne d'accuracy (/admin/intelligence/accuracy). - C'est notre preuve publique-interne du "+8 ans d'avance" : on peut montrer "on a flaggé ce produit en early_scaling 24j avant qu'il soit dans l'outil de référence".
4.3 Alerting dérive
Cron cost-alerts étendu : alerte si l'accuracy d'un prédicteur décroche (drift modèle).
5. Surfaces qui weaponisent (sinon ça dort en base)
5.1 Discovery prédictif (UI publique + dashboard)
Nouvelles surfaces /intelligence :
- Breakout Radar — produits/stores en
early_scalingtriés parbreakout_probability. - Saturation Alerts — stores/produits qui vont plafonner.
- Emerging Niches — clusters marché en émergence + fenêtre d'entrée.
- Opportunity Finder — demande↑ × concurrence↓ (le filtre tueur).
- Winning Angles — hooks créatifs en propagation.
5.2 Agent-native (le différenciateur ultime, l'IA agit)
Nouveaux tools (matrix read) :
predictBreakout(url),forecastStore(url),findOpportunities(niche?),getMarketTrends(niche),getSupplierIntel(url|product).- Proactif : Faye pousse dans
PlatformActivity"3 produits de ta niche entrent en early_scaling → @Maya peut lancer le test". Connecté à l'infra d'alertes existante + PushNotification.
5.3 MCP + API
- Tools MCP
predictBreakout,findOpportunities,getMarketTrends(tier auth payant : c'est de la valeur premium). - Routes
/api/intelligence/predict/[domain],/api/intelligence/opportunities,/api/intelligence/markets.
6. Durcissement couverture/profondeur (le moat de breadth)
- Reviews : drivers riches Yotpo, Stamped, Okendo, Trustpilot, Reviews.io (aujourd'hui pixel-only). → meilleur signal review velocity pour §2.2.
- Bulk indexing : lancer le crawl (seed lists → millions de stores). La prédiction n'a de valeur que sur une large base. Budget + rate-limit déjà gérés.
- Time-series → ClickHouse : activer le driver quand
StoreMetricDailydépasse ~50M lignes (abstraction déjà là). - Inventory velocity : élargir
variant_inventory_sniffer(sell-through = signal de demande premier ordre quand exposé). - Trends source : intégrer un signal de recherche (Google Trends non-officiel / TikTok hashtag velocity via Apify) pour
demand_momentum(§2.5), adapter fail-soft.
7. Défensibilité / future-proof (peu importe l'évolution du marché)
- Signal registry : un nouveau signal (nouvelle plateforme ad, nouveau vendor review) se branche via adapter + entrée registry, sans churn de schéma (le système de layers/sections l'absorbe). Si demain "AcmeAds" devient le réseau dominant, on ajoute un adapter : le reste ne bouge pas.
- Provider-agnostic partout (déjà : traffic, ads, seo). Aucune dépendance dure à un fournisseur.
- Flywheel documenté (§1) : la donnée OAuth + le backtesting composent, un concurrent part de zéro sur les deux.
- Accuracy publiée en interne : la preuve chiffrée que nos prédictions tiennent → argument commercial inattaquable ("voici notre precision backtestée sur 18 mois").
8. Séquencement — le prochain PR, découpé
Ordre = valeur × dépendances. Chaque étape shippe quelque chose de démontrable.
| # | Lot | Livrable démontrable | Dépend de |
|---|---|---|---|
| 1 | Feature store : StoreMetricDaily + cron prediction-rollup + backfill | séries propres par store | — |
| 2 | Section prediction (canonical mechanics) | schéma + reconcile OK | — |
| 3 | Breakout + saturation predictors + câblage cron | stage + breakout_probability sur les records | 1,2 |
| 4 | Backtest harness + PredictionAccuracy + dashboard /admin/intelligence/accuracy | courbes precision/recall | 3 |
| 5 | Forecaster (revenue/traffic, CI) + calibration sur OAuth | forecasts validés | 1,2,4 |
| 6 | Market clusters + emerging niches (MarketCluster) | Emerging Niches surface | 1 |
| 7 | Supplier intel (perceptual hash) + graph edges shared_supplier | supplier/margin par produit | graph existant |
| 8 | Opportunity scorer (demande×concurrence) + trends source | Opportunity Finder | 3,6,7 |
| 9 | Cross-store creative trends | Winning Angles radar | creative-clusterer |
| 10 | Surfaces : Breakout Radar / Saturation / Opportunity / Niches / Angles | 5 pages discovery | 3,5,6,8,9 |
| 11 | Agent tools + proactif (predictBreakout, findOpportunities, …) + alertes push | @Faye pousse des prédictions | 3,5,8 |
| 12 | MCP + API predict (tier payant) | endpoints externes | 11 |
| 13 | Durcissement : reviews vendors, bulk crawl, trends, ClickHouse | breadth + fraîcheur | parallèle |
Phase 1 du PR = lots 1-4 (feature store → breakout/saturation → backtest). C'est le minimum qui rend la prédiction réelle ET prouvée. Le reste s'empile.
9. Critères d'acceptation — "comment on sait qu'on a gagné"
- Un merchant/agent paste une URL → reçoit non seulement l'audit (déjà le cas) mais le stade de cycle de vie + une probabilité de breakout + un horizon, avec CI.
- Le dashboard d'accuracy montre une precision backtestée > seuil (ex. >0.7 sur breakout à 30j) : chiffre défendable.
- L'Opportunity Finder retourne des produits demande↑ × concurrence↓ introuvables ailleurs.
- Faye pousse proactivement des prédictions actionnables dans le cockpit.
- Comparé à n'importe quel "spy tool" : eux = liste de stores + ads passées ; nous = radar + forecast + opportunité scorée + action agent, calibré sur du ground truth. La comparaison n'a plus de sens.
10. Coûts / garde-fous
- Modèles statistiques v1 = ~0 coût marginal (compute interne). LLM réservé au sentiment + teardown (déjà budget-cappé).
- Trends/supplier providers = adapters fail-soft, env-gated (pattern §21).
- Prédiction = crédits selon le modèle existant (pre-stream estimate + hard cap). Forecasts inclus dans Unlimited, au-delà décompté.
- RGPD : agrégats marché only, pas de PII nouvelle. Cohérent avec §12 pipeline.
TL;DR, On a fini le moteur d'observation (inégalé sur Shopify). Ce PR construit le moteur de prédiction par-dessus, prouvé par backtesting, calibré par notre ground truth OAuth, actionné par les agents. C'est ça, les +8 ans d'avance : pas une feature de plus, un flywheel que personne ne peut amorcer sans nos données et notre produit.
11. Deux défauts que la prédiction se cachait à elle-même (intelligence/0317)
11.1 Le rollup quotidien ne tournait pas
runDailyRollup prenait les limit stores au plus ancien
StoreIntelligence.updatedAt et écrivait un point StoreMetricDaily
pour chacun, sous un commentaire promettant que la file « rotates fairly
across days ». Il n'écrivait jamais dans StoreIntelligence, donc il ne
faisait jamais avancer la colonne sur laquelle il triait. Les lignes
servies restaient en tête de file et étaient reservies le lendemain ;
celles au-delà de la position limit étaient inatteignables par
construction, sauf si un autre écrivain touchait leur ligne.
La population qui en souffre est exactement celle que le tri voulait
favoriser : un store que rien ne scanne est un store dont rien ne fait
avancer updatedAt. Leurs séries restaient trouées, ce qui biaise les
pentes et accélérations que le rollup existe pour lisser.
C'est le même défaut que intelligence/0239 avait trouvé dans la marche
Apify (« le cron hebdo relisait les MÊMES 20 000 premiers items chaque
dimanche, indéfiniment »), et il prend la même forme de correction :
trier sur une clé que le lot ne mute pas (primaryDomain), retenir où on
s'est arrêté, reprendre après. La borne est un primaryDomain > curseur
plutôt qu'une pagination par curseur de ligne : un curseur qui désigne
une ligne depuis supprimée reste alors une borne valide, sans code pour
rattraper le cas. Le curseur vit en KV, comme celui d'Apify et pour les
mêmes raisons : une valeur, réécrite une fois par jour, lue une fois, et
dont la perte ne coûte qu'un redémarrage du tour. Une page courte remet
le curseur au début : la marche est une boucle, pas un aller simple.
La garantie qui en découle est exactement celle-ci, et c'est celle que
le limit du cron contrôle : chaque store vivant reçoit un point tous
les ceil(vivants / limit) jours. Avec limit: 2000 et moins de 2000
stores vivants, cela vaut bien un point par jour et par store — ce qui
explique pourquoi la phrase « un point par jour pour chaque boutique » a
pu passer pour vraie aussi longtemps.
Le résumé du cron porte from, wrapped et cursorPersisted. Le
dernier est ce qui rend visible le cas où KV n'a pas retenu la position :
le tick suivant refera la même fenêtre. C'est le même mot que la marche
Apify (discovery/apify-cursor.ts), pour que l'opérateur n'ait qu'un
vocabulaire à lire.
11.2 opportunity_score n'est pas une prédiction, et le dit
backlog/_archive/intelligence/0068 posait l'alternative : « opportunity
a des lignes dans PredictionAccuracy, ou bien il cesse d'être présenté
comme une prédiction sur une surface publique. Un des deux. » Il a livré
la moitié qui regarde le code (renommage OpportunityPrediction →
OpportunityScore, en-têtes corrigés, garde statique) puis s'est archivé
done pendant que le radar public continuait d'affirmer, en six langues,
« chaque prévision porte un intervalle de confiance et est backtestée ».
Le runner n'écrit que trois modèles : breakout, saturation,
forecast_traffic. C'est correct et ce n'est pas un manque à combler :
scoreOpportunity compose des observations au présent (momentum,
densité concurrentielle, marge) et ne nomme aucune fenêtre future, donc
il n'y a pas de réalisation à étiqueter et pas de précision à mesurer.
Inventer un chiffre est précisément ce que 0068 interdit.
La classification vit donc dans le type, pas dans un littéral de test :
PUBLIC_METRIC_MODEL dans prediction/project.ts est un
Record<keyof PredictionView, BacktestModel | null>, donc ajouter un
champ à la vue sans trancher échoue au pnpm typecheck.
Côté copie, la phrase générale a été retirée de radar.intro dans les
six locales. L'affirmation positive est descendue sur les deux lentilles
qui la portent (breakout.description, saturation.description), et
opportunity.description dit ce qu'il est : « score heuristique, pas une
prédiction : pas d'horizon, pas de backtest ». Même correction sur la
description du moteur Intelligence de /features/api (qui vendait aussi
la prévision de revenu comme backtestée) et sur la description de
l'outil predictStoreTrajectory du catalogue MCP.
Le garde qui tient ça (prediction/prediction-models.test.ts) est une
liste de chemins de clés autorisés à dire « backtesté », pas un test
de formulation. Une regex qui prétendrait décider, en six langues, si une
phrase revendique un backtest ou en nie un serait une devinette déguisée
en garde : « not backtested », « nicht backgetestet » et « ne sont pas
backtestés » contiennent tous la revendication. Ce qu'une machine peut
vérifier, c'est qu'aucune chaîne NOUVELLE ne se met à la faire sans
qu'un humain l'inscrive, et que la phrase générale ne revienne pas.