mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-20 13:38:08 +02:00
feat: Introduce AI copilot for product analysis, incorporating a scoring engine, dedicated state management, and insight display.
This commit is contained in:
1 parent
15478fd07d
commit
7b95cee4c2
7 files changed
+48
-10
No files matched your search
@@ -7,15 +7,19 @@ export class AnalysisEngine {
|
|||||||
RÈGLES D'OR DE DÉCISION :
|
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.
|
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é.
|
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. VALIDATION PAR LA VALEUR : Pour recommander [A], le produit doit prouver sa valeur réelle (ex: Marge élevée, CA significatif, ou Régularité de service irréprochable). Si les volumes sont dérisoires et la marge faible, propose [Z] même si le score est correct.
|
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.
|
||||||
|
|
||||||
RÈGLES DE RÉDACTION :
|
RÈGLES DE RÉDACTION :
|
||||||
1. VÉRACITÉ : Respecte strictement les chiffres fournis.
|
1. VÉRACITÉ : Respecte strictement les chiffres fournis.
|
||||||
2. SANS COMPLAISANCE : Sois directe. Si un produit est mauvais malgré son score, dis-le.
|
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) ou le service (régularité).
|
3. JUSTIFICATION : Base ta réponse sur la rentabilité (marge), le poids business (CA/Volume) ou la typologie (Trafic vs Marge).
|
||||||
|
|
||||||
Options de recommandation :
|
Options de recommandation :
|
||||||
- [A] - PERMANENT : Produit dont la valeur business ou le service client est prouvé.
|
- [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.
|
- [C] - SAISONNIER : Pics de ventes concentrés, inactivité hors saison.
|
||||||
- [Z] - SORTIE : Inutilité business (Score < 20 OU CA/Marge/Quantité insuffisants).
|
- [Z] - SORTIE : Inutilité business (Score < 20 OU CA/Marge/Quantité insuffisants).
|
||||||
|
|
||||||
@@ -24,12 +28,19 @@ Format : "[Recommandation] : [Justification factuelle]"`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
static generateUserMessage(p: ProductAnalysisInput): string {
|
static generateUserMessage(p: ProductAnalysisInput): string {
|
||||||
|
const pmv = p.totalQuantite > 0 ? p.totalCa / p.totalQuantite : 0;
|
||||||
|
|
||||||
const monthlySummary = Object.entries(p.sales12m || {})
|
const monthlySummary = Object.entries(p.sales12m || {})
|
||||||
.map(([k, v]) => `${k}: ${Math.round(v)}u`)
|
.map(([k, v]) => `${k}: ${Math.round(v)}u`)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
const volumeInfo = `Volumes : ${Math.round(p.totalQuantite)}u sur ${p.storeCount} mag.`;
|
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
|
||||||
|
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 projectionInfo = p.regularityScore > 0 && p.regularityScore < 12 ? `
|
const projectionInfo = p.regularityScore > 0 && p.regularityScore < 12 ? `
|
||||||
--- ANALYSE DE POTENTIEL (Produit récent : ${p.regularityScore}/12 mois active) ---
|
--- 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)}€)` : "";
|
Projection 12 mois : ${Math.round(p.projectedTotalQuantite || 0)}u (${(p.projectedTotalCa || 0).toFixed(2)}€)` : "";
|
||||||
@@ -46,9 +57,10 @@ Projection 12 mois : ${Math.round(p.projectedTotalQuantite || 0)}u (${(p.project
|
|||||||
DONNÉES BUSINESS :
|
DONNÉES BUSINESS :
|
||||||
- Marge brute : ${p.tauxMarge.toFixed(1)}%
|
- Marge brute : ${p.tauxMarge.toFixed(1)}%
|
||||||
- Total CA : ${p.totalCa.toFixed(2)}€
|
- Total CA : ${p.totalCa.toFixed(2)}€
|
||||||
|
- PMV (Prix de Vente Moyen) : ${pmv.toFixed(2)}€
|
||||||
- Score Global App : ${p.score.toFixed(1)}/100
|
- Score Global App : ${p.score.toFixed(1)}/100
|
||||||
- Régularité : ${p.regularityScore}/12 mois active
|
- Régularité : ${p.regularityScore}/12 mois active
|
||||||
${volumeInfo}${projectionInfo}${activityAlert}
|
${volumeInfo}${relativeVolume}${projectionInfo}${activityAlert}
|
||||||
- Historique mensuel (Pondéré) : ${monthlySummary}
|
- Historique mensuel (Pondéré) : ${monthlySummary}
|
||||||
|
|
||||||
Analyse la pertinence business du produit sans aucune complaisance. Quelle est ta recommandation (A, C ou Z) ?`;
|
Analyse la pertinence business du produit sans aucune complaisance. Quelle est ta recommandation (A, C ou Z) ?`;
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ export function AiInsightBlock({ row }: AiInsightBlockProps) {
|
|||||||
projectedTotalCa: regScore > 0 ? (row.totalCa * weight * (12 / regScore)) : (row.totalCa * weight),
|
projectedTotalCa: regScore > 0 ? (row.totalCa * weight * (12 / regScore)) : (row.totalCa * weight),
|
||||||
lastMonthWithSale: lastMonth,
|
lastMonthWithSale: lastMonth,
|
||||||
inactivityMonths: inactivity,
|
inactivityMonths: inactivity,
|
||||||
|
avgQtyFournisseur: row.avgQtyFournisseur,
|
||||||
|
avgQtyRayon: row.avgQtyRayon,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ export interface ProductAnalysisInput {
|
|||||||
weightedTotalQuantite?: number;
|
weightedTotalQuantite?: number;
|
||||||
weightedTotalCa?: number;
|
weightedTotalCa?: number;
|
||||||
avgTotalQuantite?: number;
|
avgTotalQuantite?: number;
|
||||||
avgQtyGroup1?: number; // Moyenne volume pour produits à 1 magasin (Fournisseur)
|
avgQtyRayon?: number;
|
||||||
avgQtyGroup2?: number; // Moyenne volume pour produits à 2 magasins+ (Fournisseur)
|
avgQtyFournisseur?: number;
|
||||||
avgQtyRayon1?: number; // Moyenne volume pour produits à 1 magasin (Rayon)
|
|
||||||
avgQtyRayon2?: number; // Moyenne volume pour produits à 2 magasins+ (Rayon)
|
|
||||||
storeCount: number;
|
storeCount: number;
|
||||||
sales12m: Record<string, number>;
|
sales12m: Record<string, number>;
|
||||||
codeGamme: string | null;
|
codeGamme: string | null;
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ interface AiCopilotState {
|
|||||||
projectedTotalCa?: number;
|
projectedTotalCa?: number;
|
||||||
lastMonthWithSale?: string;
|
lastMonthWithSale?: string;
|
||||||
inactivityMonths?: number;
|
inactivityMonths?: number;
|
||||||
|
avgQtyFournisseur?: number;
|
||||||
|
avgQtyRayon?: number;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
resetInsights: () => void;
|
resetInsights: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-1
@@ -50,8 +50,25 @@ export function computeProductScores(
|
|||||||
const maxCa = Math.max(...weightedData.map((d) => d.wCa));
|
const maxCa = Math.max(...weightedData.map((d) => d.wCa));
|
||||||
const maxMarge = Math.max(...weightedData.map((d) => d.wMarge));
|
const maxMarge = Math.max(...weightedData.map((d) => d.wMarge));
|
||||||
|
|
||||||
// 2. Pour chaque produit, calculer le score relatif aux max pondérés
|
// 2. Calculer les moyennes globales et par rayon
|
||||||
|
const totalQty = weightedData.reduce((acc, d) => acc + d.wQty, 0);
|
||||||
|
const avgQtyFournisseur = rows.length > 0 ? totalQty / rows.length : 0;
|
||||||
|
|
||||||
|
const rayonTotals = new Map<string, { total: number, count: number }>();
|
||||||
|
weightedData.forEach(d => {
|
||||||
|
const r2 = d.row.code2 || "default";
|
||||||
|
const current = rayonTotals.get(r2) || { total: 0, count: 0 };
|
||||||
|
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
|
||||||
for (const data of weightedData) {
|
for (const data of weightedData) {
|
||||||
|
// Injection des moyennes pour l'analyse IA
|
||||||
|
data.row.avgQtyFournisseur = avgQtyFournisseur;
|
||||||
|
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é (le Top 1 pondéré a 100)
|
||||||
const scoreQty = maxQty > 0 ? (data.wQty / maxQty) * 100 : 0;
|
const scoreQty = maxQty > 0 ? (data.wQty / maxQty) * 100 : 0;
|
||||||
const scoreCa = maxCa > 0 ? (data.wCa / maxCa) * 100 : 0;
|
const scoreCa = maxCa > 0 ? (data.wCa / maxCa) * 100 : 0;
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ export interface ProductRow {
|
|||||||
score: number;
|
score: number;
|
||||||
workingStores: string[];
|
workingStores: string[];
|
||||||
aiRecommendation?: string | null;
|
aiRecommendation?: string | null;
|
||||||
|
/** Moyennes de comparaison pour l'IA */
|
||||||
|
avgQtyFournisseur?: number;
|
||||||
|
avgQtyRayon?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Summary bar totals for the currently visible/filtered rows */
|
/** Summary bar totals for the currently visible/filtered rows */
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ Activation et initialisation de l'Agent Master BMAD.
|
|||||||
- [x] Étape 14 : Correction du design et du positionnement du modal (z-index/Portal)
|
- [x] Étape 14 : Correction du design et du positionnement du modal (z-index/Portal)
|
||||||
- [x] Étape 15 : Polissage final et validation Michael
|
- [x] Étape 15 : Polissage final et validation Michael
|
||||||
- [x] Étape 16 : Correction graphique selon la charte CollectFlow (Slate-950/Apple)
|
- [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
|
||||||
|
|
||||||
# Progress Log
|
## Progress Log
|
||||||
|
|
||||||
- Initialisation de la tâche d'activation de l'Agent Master BMAD.
|
- Initialisation de la tâche d'activation de l'Agent Master BMAD.
|
||||||
- Variables de session chargées : Michael, French, _bmad-output.
|
- Variables de session chargées : Michael, French, _bmad-output.
|
||||||
@@ -52,3 +54,5 @@ Activation et initialisation de l'Agent Master BMAD.
|
|||||||
- Refonte visuelle complète du modal : passage aux variables CSS de `globals.css`.
|
- 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`.
|
- Suppression des bordures blanches et du fond noir pur pour le thème `Slate-950`.
|
||||||
- Intégration de `tabular-nums` pour les indicateurs financiers.
|
- 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).
|
||||||
Reference in new issue
Block a user