feat: Introduce AI copilot with a refined analysis engine for product recommendations based on strategic weight and relative contribution.

This commit is contained in:
Michael committed 2026-02-23 17:42:34 +01:00
1 parent 7b95cee4c2
commit b5b31a35af
7 files changed
+97 -84

No files matched your search

@@ -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 {
@@ -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,
});
};
@@ -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<string, number>;
codeGamme: string | null;
@@ -32,6 +32,10 @@ interface AiCopilotState {
inactivityMonths?: number;
avgQtyFournisseur?: number;
avgQtyRayon?: number;
shareCa?: number;
shareMarge?: number;
shareQty?: number;
totalFournisseurCa?: number;
}) => Promise<void>;
resetInsights: () => void;
}
+14 -4
View File
@@ -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<string, { total: number, count: number }>();
@@ -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;
+6
View File
@@ -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 */
+20 -51
View File
@@ -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.