From 661704bf3856decf44c71a6d568cb43c82907200 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 Feb 2026 12:11:32 +0100 Subject: [PATCH] feat: Add bulk AI product analysis component with supporting data types and AI system prompt. --- src/features/ai-copilot/business/analysis-engine.ts | 4 +++- src/features/ai-copilot/models/ai-analysis.types.ts | 3 +++ src/features/grid/components/bulk-ai-analyzer.tsx | 13 ++++++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/features/ai-copilot/business/analysis-engine.ts b/src/features/ai-copilot/business/analysis-engine.ts index d03bb3d..a50d28f 100644 --- a/src/features/ai-copilot/business/analysis-engine.ts +++ b/src/features/ai-copilot/business/analysis-engine.ts @@ -13,7 +13,8 @@ Critères de pondération et Règles Métier : 1. Volume Relatif (Flux de stock) : Si le volume est élevé (très supérieur à la moyenne), c'est un produit "fond de rayon" qui génère du trafic. ON LE GARDE (A ou C) même si son CA individuel est faible. 2. Mix CA/Marge (Rentabilité) : Si le volume est faible mais que le produit génère un CA conséquent et une BONNE MARGE, c'est une pépite. ON LE GARDE EN A. 3. Poids Relatif : Analyse toujours la performance du produit PAR RAPPORT AU RESTE (moyenne fournisseur). Un produit doit être Z uniquement s'il est faible sur TOUS les indicateurs (Volume, CA, Marge). -4. Régularité : La stabilité sur 12 mois est la signature du Permanent (A). +4. Contexte Magasins (Crucial) : Compare le produit à ses pairs. Si le produit est dans 1 magasin, compare son volume à la moyenne des produits à 1 magasin (avgQtyGroup1). Ne le pénalise pas parce qu'il vend moins qu'un produit présent dans 2 magasins ou plus. Un produit à 1 magasin qui performe mieux que la moyenne de son groupe est un excellent candidat A. +5. Régularité : La stabilité sur 12 mois est la signature du Permanent (A). Ta réponse doit être en 1-2 phrases maximum, en français, directe et actionnable. Format impératif : "[Recommandation]: [Justification courte]"`; @@ -30,6 +31,7 @@ Format impératif : "[Recommandation]: [Justification courte]"`; return `Produit : "${p.libelle1}" (Ref: ${p.codein}) Gamme actuelle : ${p.codeGamme ?? "Non définie"} +Contexte : Données basées sur ${p.storeCount} magasin(s). Indicateurs 12m : - CA : ${p.totalCa.toFixed(2)}€ - Marge : ${p.tauxMarge.toFixed(1)}% diff --git a/src/features/ai-copilot/models/ai-analysis.types.ts b/src/features/ai-copilot/models/ai-analysis.types.ts index 3060e45..782c5b6 100644 --- a/src/features/ai-copilot/models/ai-analysis.types.ts +++ b/src/features/ai-copilot/models/ai-analysis.types.ts @@ -5,6 +5,9 @@ export interface ProductAnalysisInput { tauxMarge: number; totalQuantite: number; avgTotalQuantite?: number; + avgQtyGroup1?: number; // Moyenne volume pour produits à 1 magasin + avgQtyGroup2?: number; // Moyenne volume pour produits à 2 magasins+ + storeCount: number; sales12m: Record; codeGamme: string | null; } diff --git a/src/features/grid/components/bulk-ai-analyzer.tsx b/src/features/grid/components/bulk-ai-analyzer.tsx index c3fc49a..b5bb03c 100644 --- a/src/features/grid/components/bulk-ai-analyzer.tsx +++ b/src/features/grid/components/bulk-ai-analyzer.tsx @@ -20,12 +20,16 @@ export function BulkAiAnalyzer() { }; const handleAnalyze = async () => { - const { rows } = useGridStore.getState(); - isCancelledRef.current = false; - const totalQtySum = rows.reduce((sum, r) => sum + (r.totalQuantite || 0), 0); const avgQty = rows.length > 0 ? totalQtySum / rows.length : 0; + // Benchmarks par groupe de magasins + const rowsGroup1 = rows.filter(r => (r.workingStores?.length || 1) === 1); + const rowsGroup2 = rows.filter(r => (r.workingStores?.length || 1) >= 2); + + const avgQty1 = rowsGroup1.length > 0 ? rowsGroup1.reduce((s, r) => s + (r.totalQuantite || 0), 0) / rowsGroup1.length : 0; + const avgQty2 = rowsGroup2.length > 0 ? rowsGroup2.reduce((s, r) => s + (r.totalQuantite || 0), 0) / rowsGroup2.length : 0; + const productPayloads: ProductAnalysisInput[] = rows.map(r => ({ codein: r.codein, libelle1: r.libelle1 || "", @@ -33,6 +37,9 @@ export function BulkAiAnalyzer() { tauxMarge: r.tauxMarge || 0, totalQuantite: r.totalQuantite || 0, avgTotalQuantite: avgQty, + avgQtyGroup1: avgQty1, + avgQtyGroup2: avgQty2, + storeCount: r.workingStores?.length || 1, sales12m: r.sales12m || {}, codeGamme: r.codeGamme || null, }));