ArchitectureIntelligence — Prediction Engine Roadmap (le "+8 ans d'avance")

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 : MarketCluster Prisma + module market/ (agrégation niche, emerging-niche detection) + cron market-aggregate ; supplier/ (cohorte fournisseur via handles + stems d'images partagés, score commodité, likely-dropship) + signal graph product_image_stem ; creative-trends/ (getWinningAngles, hooks se propageant inter-marques) ; driver review Trustpilot réel (page publique, alimente velocity + sentiment) ; proactif cron prediction-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èles StoreMetricDaily + PredictionAccuracy ; moteur prediction/ (series-math, breakout/lifecycle, saturation, Holt forecast, opportunity scorer) ; rollup + backfill depuis snapshots ; backtest harness + cron prediction-backtest ; câblage orchestrateur (pass 4 post-graph) ; tools agents predictStoreTrajectory + findOpportunities + tool MCP predictStoreTrajectory ; 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 + PinterestMulti-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 IntelligenceStoreSignalIndex + 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-seriesAdActivitySnapshot, CatalogDelta, IntelligencePriceObservation, IntelligencePanelEvent.
Refresh tiers HOT/WARM/COLD + bulk-indexerCrons + 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=30 jamais 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) :

  1. Toute prédiction porte un intervalle de confiance + un horizon. Jamais un chiffre nu. (Honnêteté = signal différenciant, cf. §7 pipeline.)
  2. Toute prédiction est backtestée. On ne shippe pas un prédicteur sans sa courbe d'accuracy historique.
  3. 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éSourcePourquoi c'est précoce
Accélération du nb de créatives (2e dérivée > 0)AdActivitySnapshotun store qui multiplie ses créas teste un gagnant
Hausse du taux de lancement de créascreative timestampsitération = signal de scaling
Inflexion review velocityreviews recent_count_30d sérieventes réelles qui accélèrent
Momentum social (followers/mentions ↑)social-metrics sériedemande amont
Apparition de nouveaux marchés/pays en adsads countries deltaexpansion géo = confiance
Prix stable + discount faiblecommerce sérieearly 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, lisent StoreMetricDaily + le record, et émettent la section prediction (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 kind prediction + 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.

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_scaling triés par breakout_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 StoreMetricDaily dé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.

#LotLivrable démontrableDépend de
1Feature store : StoreMetricDaily + cron prediction-rollup + backfillséries propres par store—
2Section prediction (canonical mechanics)schéma + reconcile OK—
3Breakout + saturation predictors + câblage cronstage + breakout_probability sur les records1,2
4Backtest harness + PredictionAccuracy + dashboard /admin/intelligence/accuracycourbes precision/recall3
5Forecaster (revenue/traffic, CI) + calibration sur OAuthforecasts validés1,2,4
6Market clusters + emerging niches (MarketCluster)Emerging Niches surface1
7Supplier intel (perceptual hash) + graph edges shared_suppliersupplier/margin par produitgraph existant
8Opportunity scorer (demande×concurrence) + trends sourceOpportunity Finder3,6,7
9Cross-store creative trendsWinning Angles radarcreative-clusterer
10Surfaces : Breakout Radar / Saturation / Opportunity / Niches / Angles5 pages discovery3,5,6,8,9
11Agent tools + proactif (predictBreakout, findOpportunities, …) + alertes push@Faye pousse des prédictions3,5,8
12MCP + API predict (tier payant)endpoints externes11
13Durcissement : reviews vendors, bulk crawl, trends, ClickHousebreadth + fraîcheurparallè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é"

  1. 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.
  2. Le dashboard d'accuracy montre une precision backtestée > seuil (ex. >0.7 sur breakout à 30j) : chiffre défendable.
  3. L'Opportunity Finder retourne des produits demande↑ × concurrence↓ introuvables ailleurs.
  4. Faye pousse proactivement des prédictions actionnables dans le cockpit.
  5. 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.