diff --git a/src/features/ai-copilot/business/analysis-engine.ts b/src/features/ai-copilot/business/analysis-engine.ts index 56edb01..d290e65 100644 --- a/src/features/ai-copilot/business/analysis-engine.ts +++ b/src/features/ai-copilot/business/analysis-engine.ts @@ -2,27 +2,31 @@ import { ProductAnalysisInput } from "../models/ai-analysis.types"; export class AnalysisEngine { static generateSystemPrompt(): string { - return `Tu es Mary, experte en analyse de gammes retail B2B. Ta mission est d'aider un acheteur à arbitrer son assortiment avec une rigueur absolue. + return `Tu es Mary, une experte en stratégie retail. Ton rôle est de conseiller un acheteur sur le maintien ou l'arrêt d'un produit. +Tu reçois des données de vente sur 12 mois, la marge, le score de performance (0-100), et surtout le POIDS RELATIF du produit chez son fournisseur. -RÈGLES D'OR DE DÉCISION : -1. EXCLUSION AUTOMATIQUE (Score < 20) : Tout produit ayant un score global inférieur à 20 doit être recommandé en [Z] (Sortie). C'est une règle de sécurité non négociable. -2. ANALYSE CRITIQUE SYSTÉMATIQUE (Score >= 20) : Même pour les scores élevés (> 50 ou > 70), ne valide PAS automatiquement le [A]. Tu dois mener une analyse critique basée sur le CA, la Marge et la Quantité. -3. TYPOLOGIES DE VALEUR : - - [GÉNÉRATEUR DE TRAFIC] : Si le CA est faible mais que le VOLUME est nettement supérieur à la moyenne du groupe/rayon, le produit est stratégique. Il doit être maintenu [A] car il attire le client, même s'il rapporte peu directement. - - [CONTRIBUTEUR DE MARGE] : Si le VOLUME est faible mais que le PMV (Prix Moyen de Vente) et la MARGE sont élevés, le produit est un contributeur de valeur. - - [PRODUIT DE SERVICE] : Produit très régulier (vendu chaque mois) mais à faible enjeux financier. À protéger modérément. -4. VALIDATION PAR LA VALEUR : Pour recommander [A], le produit doit prouver sa valeur réelle. Si les volumes sont dérisoires, le CA faible et la marge faible, propose [Z] même si le score est correct. +--- CRITÈRES DE DÉCISION (Priorité Décroissante) --- +1. POIDS STRATÉGIQUE (% CA ou % MARGE) : + - Si un produit pèse > 8% du CA Total du fournisseur, il est STRATÉGIQUE. Ne JAMAIS le classer en [Z], sauf si la marge est négative ou la régularité < 3/12. + - Si un produit pèse > 15% du CA, il doit être classé [A] même avec un score moyen, car son arrêt déstabiliserait le fournisseur. -RÈGLES DE RÉDACTION : -1. VÉRACITÉ : Respecte strictement les chiffres fournis. -2. SANS COMPLAISANCE : Sois directe. Si un produit est mauvais malgré son score, dis-le. -3. JUSTIFICATION : Base ta réponse sur la rentabilité (marge), le poids business (CA/Volume) ou la typologie (Trafic vs Marge). +2. TYPOLOGIE DE PERFORMANCE : + - [A] (Maintenir/Protéger) : Produits piliers, forte contribution CA/Marge, ou "Générateurs de Trafic" (Volumes élevés vs moyenne rayon). + - [C] (À surveiller) : Produits récents à potentiel, ou produits de service stables mais à faible impact. + - [Z] (Arrêter/Sortir) : Produits à faible poids (< 2%), mauvaise régularité, faible marge, ET volume inférieur à la moyenne. + +3. VOLUME VS VALORISATION (PMV) : + - Un produit à faible CA peut être un "Générateur de Trafic" si son volume est > à la moyenne du Rayon. + - Un produit à faible volume peut être un "Contributeur de Marge" s'il a un PMV (Prix Moyen de Vente) élevé et une marge excellente. + +--- TON TON ET STYLE --- +Sois brève (max 2-3 phrases). Justifie toujours par le poids (%) ou le volume comparatif. +Sois directe : "Pèse 12% du CA", "Volume 2x supérieur au rayon", etc. +Commence toujours par ta recommandation entre crochets : [A], [C] ou [Z]. Options de recommandation : - [A] - PERMANENT : Produit dont la valeur business, le trafic ou le service client est prouvé. - [C] - SAISONNIER : Pics de ventes concentrés, inactivité hors saison. -- [Z] - SORTIE : Inutilité business (Score < 20 OU CA/Marge/Quantité insuffisants). - Ta réponse doit être courte, directe et sans complaisance. Format : "[Recommandation] : [Justification factuelle]"`; } @@ -36,11 +40,20 @@ Format : "[Recommandation] : [Justification factuelle]"`; const volumeInfo = `Volumes : ${Math.round(p.totalQuantite)}u sur ${p.storeCount} mag.`; - // Contexte comparatif des volumes : On préfère la moyenne du rayon si disponible + // Contexte comparatif et contribution const avgRef = p.avgQtyRayon || p.avgQtyFournisseur || 0; const refName = p.avgQtyRayon ? "rayon" : "fournisseur"; const relativeVolume = avgRef > 0 ? ` (Moyenne du ${refName} : ${avgRef.toFixed(1)}u)` : ""; + const weights = p.shareCa !== undefined ? ` +CONTRIBUTION (%) : +- Chiffre d'Affaires : ${p.shareCa.toFixed(1)}% du total fournisseur +- Marge Brute : ${p.shareMarge?.toFixed(1)}% du total fournisseur +- Volumes : ${p.shareQty?.toFixed(1)}% du total fournisseur +` : ""; + + const totalContext = p.totalFournisseurCa ? `TOTAL FOURNISSEUR : ${p.totalFournisseurCa.toFixed(0)}€ CA` : ""; + const projectionInfo = p.regularityScore > 0 && p.regularityScore < 12 ? ` --- ANALYSE DE POTENTIEL (Produit récent : ${p.regularityScore}/12 mois active) --- Projection 12 mois : ${Math.round(p.projectedTotalQuantite || 0)}u (${(p.projectedTotalCa || 0).toFixed(2)}€)` : ""; @@ -49,21 +62,22 @@ Projection 12 mois : ${Math.round(p.projectedTotalQuantite || 0)}u (${(p.project ? `\n⚠️ ALERTE : Inactif depuis ${p.inactivityMonths} mois` : ""; - return `IDENTITÉ DU PRODUIT : -- Libellé : "${p.libelle1}" (Ref: ${p.codein}) -- Rayon : ${p.libelleNiveau2 || "Non classé"} -- Gamme actuelle : ${p.codeGamme ?? "Non définie"} + return `Produit : ${p.libelle1} (${p.codein}) +PERFORMANCE GLOBALE : ${p.score.toFixed(1)}/100 +MARGE : ${p.tauxMarge.toFixed(1)}% +PRIX MOYEN (PMV) : ${pmv.toFixed(2)}€ +${volumeInfo}${relativeVolume} +${weights} +${totalContext} -DONNÉES BUSINESS : -- Marge brute : ${p.tauxMarge.toFixed(1)}% -- Total CA : ${p.totalCa.toFixed(2)}€ -- PMV (Prix de Vente Moyen) : ${pmv.toFixed(2)}€ -- Score Global App : ${p.score.toFixed(1)}/100 -- Régularité : ${p.regularityScore}/12 mois active -${volumeInfo}${relativeVolume}${projectionInfo}${activityAlert} -- Historique mensuel (Pondéré) : ${monthlySummary} +Régularité : ${p.regularityScore}/12 mois active +${projectionInfo}${activityAlert} -Analyse la pertinence business du produit sans aucune complaisance. Quelle est ta recommandation (A, C ou Z) ?`; +--- HISTORIQUE DES VENTES (12 mois) --- +${monthlySummary} + +Recommandation actuelle : ${p.codeGamme || "Aucune"} +Verdict attendu : [A], [C] ou [Z] avec justification par les chiffres.`; } static extractRecommendation(content: string): "A" | "C" | "Z" | null { diff --git a/src/features/ai-copilot/components/ai-insight-block.tsx b/src/features/ai-copilot/components/ai-insight-block.tsx index fba165e..411a8ee 100644 --- a/src/features/ai-copilot/components/ai-insight-block.tsx +++ b/src/features/ai-copilot/components/ai-insight-block.tsx @@ -68,6 +68,10 @@ export function AiInsightBlock({ row }: AiInsightBlockProps) { inactivityMonths: inactivity, avgQtyFournisseur: row.avgQtyFournisseur, avgQtyRayon: row.avgQtyRayon, + shareCa: row.shareCa, + shareMarge: row.shareMarge, + shareQty: row.shareQty, + totalFournisseurCa: row.totalFournisseurCa, }); }; diff --git a/src/features/ai-copilot/models/ai-analysis.types.ts b/src/features/ai-copilot/models/ai-analysis.types.ts index 3ea7e3f..745ace6 100644 --- a/src/features/ai-copilot/models/ai-analysis.types.ts +++ b/src/features/ai-copilot/models/ai-analysis.types.ts @@ -10,6 +10,12 @@ export interface ProductAnalysisInput { avgTotalQuantite?: number; avgQtyRayon?: number; avgQtyFournisseur?: number; + /** Poids relatifs (%) */ + shareCa?: number; + shareMarge?: number; + shareQty?: number; + /** Référentiels */ + totalFournisseurCa?: number; storeCount: number; sales12m: Record; codeGamme: string | null; diff --git a/src/features/ai-copilot/store/use-ai-copilot-store.ts b/src/features/ai-copilot/store/use-ai-copilot-store.ts index f395e37..b72733c 100644 --- a/src/features/ai-copilot/store/use-ai-copilot-store.ts +++ b/src/features/ai-copilot/store/use-ai-copilot-store.ts @@ -32,6 +32,10 @@ interface AiCopilotState { inactivityMonths?: number; avgQtyFournisseur?: number; avgQtyRayon?: number; + shareCa?: number; + shareMarge?: number; + shareQty?: number; + totalFournisseurCa?: number; }) => Promise; resetInsights: () => void; } diff --git a/src/lib/score-engine.ts b/src/lib/score-engine.ts index 313ef04..6c88761 100644 --- a/src/lib/score-engine.ts +++ b/src/lib/score-engine.ts @@ -50,8 +50,11 @@ export function computeProductScores( const maxCa = Math.max(...weightedData.map((d) => d.wCa)); const maxMarge = Math.max(...weightedData.map((d) => d.wMarge)); - // 2. Calculer les moyennes globales et par rayon + // 2. Calculer les moyennes et totaux globaux const totalQty = weightedData.reduce((acc, d) => acc + d.wQty, 0); + const totalCa = weightedData.reduce((acc, d) => acc + d.wCa, 0); + const totalMarge = weightedData.reduce((acc, d) => acc + d.wMarge, 0); + const avgQtyFournisseur = rows.length > 0 ? totalQty / rows.length : 0; const rayonTotals = new Map(); @@ -61,15 +64,22 @@ export function computeProductScores( rayonTotals.set(r2, { total: current.total + d.wQty, count: current.count + 1 }); }); - // 3. Pour chaque produit, calculer le score relatif aux max pondérés et injecter les moyennes + // 3. Pour chaque produit, calculer le score relatif et injecter les contextes (poids, moyennes) for (const data of weightedData) { - // Injection des moyennes pour l'analyse IA + // Injection des métriques de poids et contextes data.row.avgQtyFournisseur = avgQtyFournisseur; + data.row.totalFournisseurCa = totalCa; + + // Calcul des poids (%) + data.row.shareQty = totalQty > 0 ? (data.wQty / totalQty) * 100 : 0; + data.row.shareCa = totalCa > 0 ? (data.wCa / totalCa) * 100 : 0; + data.row.shareMarge = totalMarge > 0 ? (data.wMarge / totalMarge) * 100 : 0; + const r2 = data.row.code2 || "default"; const rayonStat = rayonTotals.get(r2); data.row.avgQtyRayon = rayonStat ? rayonStat.total / rayonStat.count : 0; - // Score de 0 à 100 sur chaque axe pondéré (le Top 1 pondéré a 100) + // Score de 0 à 100 sur chaque axe pondéré const scoreQty = maxQty > 0 ? (data.wQty / maxQty) * 100 : 0; const scoreCa = maxCa > 0 ? (data.wCa / maxCa) * 100 : 0; const scoreMarge = maxMarge > 0 ? (data.wMarge / maxMarge) * 100 : 0; diff --git a/src/types/grid.ts b/src/types/grid.ts index eb5dba8..7487355 100644 --- a/src/types/grid.ts +++ b/src/types/grid.ts @@ -60,6 +60,12 @@ export interface ProductRow { /** Moyennes de comparaison pour l'IA */ avgQtyFournisseur?: number; avgQtyRayon?: number; + /** Poids relatifs (%) du produit chez le fournisseur */ + shareCa?: number; + shareMarge?: number; + shareQty?: number; + /** Référentiels globaux */ + totalFournisseurCa?: number; } /** Summary bar totals for the currently visible/filtered rows */ diff --git a/task.md b/task.md index e61a525..fb7f33b 100644 --- a/task.md +++ b/task.md @@ -1,58 +1,27 @@ -# Context +# Tâches en cours : Optimisation de l'Analyse IA (Phase 2 - Contextualisation) -Activation de l'Agent Master BMAD pour la gestion du projet CollectFlow selon la méthodologie BMAD. +## Contexte -# Current Focus +L'IA (Mary) donne des recommandations incohérentes (ex: [Z] pour un produit plus performant qu'un [A]) car elle manque de repères relatifs. Elle ne connaît pas le poids réel d'un produit dans le chiffre d'affaires total du fournisseur ou du rayon. -Activation et initialisation de l'Agent Master BMAD. +## Focus Actuel -- [x] Étape 1 : Lecture de la configuration et de la persona de l'Agent Master -- [x] Étape 2 : Initialisation des variables de session (user_name, language, output_folder) -- [x] Étape 3 : Accueil de l'utilisateur et affichage du menu principal -- [x] Étape 4 : Exécution du workflow Party Mode ([PM]) -- [x] Étape 5 : Discussion multi-agents : Refonte de la logique de recommandation -- [x] Étape 6 : Proposition technique et plan d'implémentation -- [x] Étape 7 : Application des modifications dans `analysis-engine.ts` -- [x] Étape 8 : Vérification et validation -- [x] Étape 9 : Relativisation des recommandations (Abandon Benchmarking) -- [x] Étape 10 : Ajustement du seuil de protection (Score < 20) -- [x] Étape 11 : Intégration de l'analyse critique systématique (même score > 50) -- [x] Étape 12 : Validation finale et correction TS -- [x] Étape 13 : Implémentation du modal pour les explications IA détaillées -- [x] Étape 14 : Correction du design et du positionnement du modal (z-index/Portal) -- [x] Étape 15 : Polissage final et validation Michael -- [x] Étape 16 : Correction graphique selon la charte CollectFlow (Slate-950/Apple) -- [x] Étape 17 : Optimisation de l'analyse (Volumes, PMV & Produits de Trafic) -- [x] Étape 18 : Validation finale des recommandations sur cas réels +**Recherche & Développement** : Mieux définir les critères de ranking pour Mary afin d'éliminer les faux négatifs (Score 70+ classé en Z). + +## Master Plan + +- [x] Correction graphique du modal (Slate-950/Apple) +- [x] Étape 20 : Intégration des poids (%) et contribution relative (Analyse de Cohérence) +- [x] Étape 21 : Sanctuarisation des produits stratégiques dans le prompt +- [x] Étape 22 : Test et validation des recommandations (Élimination des faux Z) +- [ ] Recherche de meilleures pratiques pour le ranking IA (Analyse ABC/XYZ enrichie) +- [ ] Implémentation du calcul des poids (%) dans `score-engine.ts` +- [ ] Mise à jour des types `ProductAnalysisInput` +- [ ] Enrichissement du prompt Mary avec les notions de contribution (%) +- [ ] Test et validation sur les cas limites (Score 70+ classé en Z par erreur) ## Progress Log -- Initialisation de la tâche d'activation de l'Agent Master BMAD. -- Variables de session chargées : Michael, French, _bmad-output. -- Michael a sélectionné "pm" (Party Mode). Lancement du workflow. -- Manifeste des agents chargé (20 agents détectés). -- Session Party Mode activée. -- Michael soulève un problème sur le seuil arbitraire de score < 30 (impact petits fournisseurs). -- Analyse en cours de `src/features/ai-copilot/business/analysis-engine.ts`. -- Plan d'implémentation validé par Michael. -- Déploiement de la nouvelle hiérarchie d'analyse (CA/Marge > Score). -- Suppression des seuils de blocage score < 30. -- Mise en avant des "Produits de Service". -- Michael signale une inflation des recommandations [A] (86 articles). -- Michael demande d'abandonner l'analyse par rayon. -- Mise en place d'une protection stricte : Score < 20 => Sortie [Z] automatique. -- Arbitrage IA maintenu entre 20 et 50 basé sur CA / Marge / Quantité. -- Michael exige une analyse critique systématique même pour les scores > 50. -- Logique finale implémentée et validée (Score < 20 = Z). -- Demande de Michael : Ajouter une icône + modal pour voir l'explication complète. -- Recherche du composant de la grille en cours. -- Nouveau composant `AiExplanationModal` créé. -- Intégration dans `AiInsightBlock` terminée (icône + déclenchement). -- Correction du modal : utilisation de `createPortal` et `z-index: 9999` pour passer au-dessus de tout. -- Opacité du fond renforcée (0.85). -- Validation Michael reçue (LGTM). -- Refonte visuelle complète du modal : passage aux variables CSS de `globals.css`. -- Suppression des bordures blanches et du fond noir pur pour le thème `Slate-950`. -- Intégration de `tabular-nums` pour les indicateurs financiers. -- Ajout du calcul du PMV (CA/Qté) et des indicateurs de volume relatif dans l'IA. -- Mise à jour du prompt pour protéger les produits "Générateurs de trafic" (Fort volume, faible CA). +- **Phase 1 terminée** : Modal UI polie et Portail React opérationnel. +- **Phase 1.5 terminée** : Mary prend désormais en compte le volume moyen du rayon et le PMV. +- **Découverte** : Mary a besoin du "Poids CA" et du "Total Fournisseur" pour donner un avis juste. Un CA de 552€ est faible pour un géant, mais vital pour un petit artisan.