mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
feat: Introduce AI copilot with a refined analysis engine for product recommendations based on strategic weight and relative contribution.
This commit is contained in:
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
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user