mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
feat(ai-copilot): add AI analysis engine with prompt generation, recommendation extraction, and insight cleaning capabilities.
This commit is contained in:
1 parent
b5b31a35af
commit
fbad53df36
2 files changed
+76
-97
No files matched your search
@@ -25,77 +25,64 @@ export default function AiAnalysisDocPage() {
|
|||||||
|
|
||||||
{/* Grid of Rules */}
|
{/* Grid of Rules */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{/* 1. Équité Territoriale */}
|
{/* 1. Loi de Pareto */}
|
||||||
<DocCard
|
<DocCard
|
||||||
icon={ShieldCheck}
|
icon={ShieldCheck}
|
||||||
title="Normalisation Magasins (x2)"
|
title="Loi de Pareto (Poids %)"
|
||||||
description="Équilibre les chances entre les produits mono-magasin et multi-magasins."
|
description="Priorité n°1 : Le poids du produit chez le fournisseur."
|
||||||
accent="teal"
|
accent="teal"
|
||||||
>
|
>
|
||||||
<p className="text-[13px] leading-relaxed">
|
<p className="text-[13px] leading-relaxed">
|
||||||
Pour éviter qu'un produit présent dans un seul magasin ne soit pénalisé par son volume brut, le système
|
L'IA identifie les produits <strong>Indiscutables</strong>. Si un produit pèse
|
||||||
<strong> double automatiquement</strong> toutes ses statistiques (Ventes, CA, Marge).
|
<strong> {"> 10%"}</strong> du CA total de son fournisseur, il est jugé stratégique.
|
||||||
Cela simule sa performance s'il était présent sur les deux points de vente de référence.
|
Mary refusera de suggérer sa sortie [Z] pour protéger la relation fournisseur et
|
||||||
|
garantir la stabilité de l'assortiment.
|
||||||
</p>
|
</p>
|
||||||
</DocCard>
|
</DocCard>
|
||||||
|
|
||||||
{/* 2. Run Rate */}
|
{/* 2. Équité & Normalisation */}
|
||||||
<DocCard
|
|
||||||
icon={TrendingUp}
|
|
||||||
title="Potentiel Annuel (Run Rate)"
|
|
||||||
description="Évalue les lancements sur leur dynamique réelle, pas sur leur cumul."
|
|
||||||
accent="indigo"
|
|
||||||
>
|
|
||||||
<p className="text-[13px] leading-relaxed">
|
|
||||||
Si un produit a moins de 12 mois de présence, l'IA calcule un <strong>Run Rate 12 mois</strong>.
|
|
||||||
<br /><br />
|
|
||||||
<code className="bg-[var(--bg-muted)] px-2 py-0.5 rounded text-indigo-600 dark:text-indigo-400 font-mono text-[11px]">
|
|
||||||
Projection = Volume_Réel * (12 / Mois_Présence)
|
|
||||||
</code>
|
|
||||||
<br /><br />
|
|
||||||
Cela permet de confirmer un produit <strong>Permanent [A]</strong> dès ses premiers mois s'il a une forte vélocité.
|
|
||||||
</p>
|
|
||||||
</DocCard>
|
|
||||||
|
|
||||||
{/* 3. Saisonnalité */}
|
|
||||||
<DocCard
|
|
||||||
icon={Calendar}
|
|
||||||
title="Détection Saisonnalité"
|
|
||||||
description="Distingue les ruptures temporaires des fins de vie ou saisons."
|
|
||||||
accent="amber"
|
|
||||||
>
|
|
||||||
<p className="text-[13px] leading-relaxed">
|
|
||||||
L'IA analyse la <strong>récence</strong> des ventes. Si aucune vente n'est constatée depuis plus de
|
|
||||||
<strong> 2 mois</strong> :
|
|
||||||
</p>
|
|
||||||
<ul className="mt-2 space-y-1 text-[13px]">
|
|
||||||
<li className="flex items-center gap-2">
|
|
||||||
<Zap className="w-3 h-3 text-emerald-500" />
|
|
||||||
<span>{"< 2 mois : Lancement probable (actif)"}</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex items-center gap-2">
|
|
||||||
<AlertTriangle className="w-3 h-3 text-amber-500" />
|
|
||||||
<span>{"> 2 mois : Alerte Saison [C] ou Sortie [Z]"}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</DocCard>
|
|
||||||
|
|
||||||
{/* 4. Score Global */}
|
|
||||||
<DocCard
|
<DocCard
|
||||||
icon={Scale}
|
icon={Scale}
|
||||||
title="Score de Performance"
|
title="Normalisation & Équité"
|
||||||
description="La source de vérité mathématique (0 à 100)."
|
description="Égalité de traitement entre mono et multi-magasins."
|
||||||
accent="rose"
|
accent="rose"
|
||||||
>
|
>
|
||||||
<p className="text-[13px] leading-relaxed">
|
<p className="text-[13px] leading-relaxed">
|
||||||
Le score affiché dans la grille combine la Quantité, le CA et la Marge.
|
Pour une comparaison juste, les statistiques des produits présents sur un seul magasin sont
|
||||||
L'IA utilise ce score comme garde-fou :
|
<strong> doublées (x2)</strong>. L'IA compare ensuite ces données pondérées aux
|
||||||
|
<strong> Benchmarks du Rayon</strong> (Moyenne du Niveau 2) pour détecter les sur-performances.
|
||||||
|
</p>
|
||||||
|
</DocCard>
|
||||||
|
|
||||||
|
{/* 3. Cycle de Vie */}
|
||||||
|
<DocCard
|
||||||
|
icon={TrendingUp}
|
||||||
|
title="Potentiel & Cycle de Vie"
|
||||||
|
description="Détection des lancements et des fins de vie."
|
||||||
|
accent="indigo"
|
||||||
|
>
|
||||||
|
<p className="text-[13px] leading-relaxed">
|
||||||
|
Mary distingue les produits récents des produits établis :
|
||||||
</p>
|
</p>
|
||||||
<ul className="mt-2 space-y-1 text-[13px]">
|
<ul className="mt-2 space-y-1 text-[13px]">
|
||||||
<li>• <strong>{"> 70"}</strong> : Candidat naturel au maintien.</li>
|
<li>• <strong>Run Rate</strong> : Projection sur 12 mois pour les produits de moins d'un an.</li>
|
||||||
<li>• <strong>{"< 30"}</strong> : Signal fort de déréférencement.</li>
|
<li>• <strong>Inactivité</strong> : Une alerte est levée après <strong>2 mois sans vente</strong>,
|
||||||
|
orientant le verdict vers le [C] (Saisonnier) ou [Z] (Sortie).</li>
|
||||||
</ul>
|
</ul>
|
||||||
</DocCard>
|
</DocCard>
|
||||||
|
|
||||||
|
{/* 4. PMV & Trafic */}
|
||||||
|
<DocCard
|
||||||
|
icon={Zap}
|
||||||
|
title="Volumes vs Valeur (PMV)"
|
||||||
|
description="Identifier les générateurs de trafic."
|
||||||
|
accent="amber"
|
||||||
|
>
|
||||||
|
<p className="text-[13px] leading-relaxed">
|
||||||
|
Un produit peut être maintenu même avec un faible CA s'il génère du <strong>Trafic</strong> (Volumes {">"} Moyenne Rayon).
|
||||||
|
À l'inverse, un produit à faible volume peut être un <strong>Contributeur de Marge</strong> précieux s'il possède un Prix Moyen de Vente (PMV) élevé.
|
||||||
|
</p>
|
||||||
|
</DocCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer / Tip */}
|
{/* Footer / Tip */}
|
||||||
|
|||||||
@@ -2,82 +2,74 @@ import { ProductAnalysisInput } from "../models/ai-analysis.types";
|
|||||||
|
|
||||||
export class AnalysisEngine {
|
export class AnalysisEngine {
|
||||||
static generateSystemPrompt(): string {
|
static generateSystemPrompt(): string {
|
||||||
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.
|
return `Tu es Mary, une experte Senior en Stratégie Retail et Category Management. Ton rôle est d'arbitrer l'assortiment avec une rigueur de consultant.
|
||||||
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.
|
Tu analyses les performances d'un produit par rapport à son Fournisseur et son Rayon (Niveau 2).
|
||||||
|
|
||||||
--- CRITÈRES DE DÉCISION (Priorité Décroissante) ---
|
--- HIÉRARCHIE DE DÉCISION MÉTIER (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.
|
|
||||||
|
|
||||||
2. TYPOLOGIE DE PERFORMANCE :
|
1. INDISCUTABLES (Poids Stratégique > 10% du CA Fournisseur) :
|
||||||
- [A] (Maintenir/Protéger) : Produits piliers, forte contribution CA/Marge, ou "Générateurs de Trafic" (Volumes élevés vs moyenne rayon).
|
- Ce sont les "Piliers" du fournisseur. Ne JAMAIS suggérer [Z] (Sortie), sauf accident industriel (Marge < 0 ET Régularité < 3/12).
|
||||||
- [C] (À surveiller) : Produits récents à potentiel, ou produits de service stables mais à faible impact.
|
- Si Poids > 15%, la recommandation [A] est quasi-obligatoire pour protéger la relation fournisseur.
|
||||||
- [Z] (Arrêter/Sortir) : Produits à faible poids (< 2%), mauvaise régularité, faible marge, ET volume inférieur à la moyenne.
|
|
||||||
|
|
||||||
3. VOLUME VS VALORISATION (PMV) :
|
2. PILIERS & VACHES À LAIT (Score > 65 + Régularité > 8/12) :
|
||||||
- Un produit à faible CA peut être un "Générateur de Trafic" si son volume est > à la moyenne du Rayon.
|
- Produits performants et stables. Recommandation [A] (Permanent).
|
||||||
- Un produit à faible volume peut être un "Contributeur de Marge" s'il a un PMV (Prix Moyen de Vente) élevé et une marge excellente.
|
- Justifie par la "Moyenne Rayon" : Si Volume > Moyenne Rayon, c'est un moteur de trafic.
|
||||||
|
|
||||||
--- TON TON ET STYLE ---
|
3. DILEMMES & SAISONNIERS (Score moyen OU Régularité faible OU Inactivité > 2) :
|
||||||
Sois brève (max 2-3 phrases). Justifie toujours par le poids (%) ou le volume comparatif.
|
- [C] (À surveiller) : Produits récents avec fort "Run Rate" (Projection 12m) ou produits au comportement saisonnier cyclique.
|
||||||
Sois directe : "Pèse 12% du CA", "Volume 2x supérieur au rayon", etc.
|
- Un produit récent (Régularité < 6) mais avec une projection 12m élevée mérite d'être protégé [C] pour lui laisser sa chance.
|
||||||
Commence toujours par ta recommandation entre crochets : [A], [C] ou [Z].
|
|
||||||
|
|
||||||
Options de recommandation :
|
4. POIDS MORTS (Poids < 2% + Score < 30 + Volume < Moyenne Rayon) :
|
||||||
- [A] - PERMANENT : Produit dont la valeur business, le trafic ou le service client est prouvé.
|
- [Z] (Sortie) : Produits marginaux qui encombrent le linéaire sans rentabilité ni débit suffisant.
|
||||||
- [C] - SAISONNIER : Pics de ventes concentrés, inactivité hors saison.
|
|
||||||
Ta réponse doit être courte, directe et sans complaisance.
|
--- TON, STYLE & FORMAT ---
|
||||||
Format : "[Recommandation] : [Justification factuelle]"`;
|
- Sois chirurgicale : Cite toujours le poids (%) et compare le volume au benchmark.
|
||||||
|
- Maximum 2-3 phrases. Pas de blabla, juste des faits.
|
||||||
|
- Format strict : "[Recommandation] : [Justification factuelle]"
|
||||||
|
- Exemple : "[A] : Produit pilier (12% du CA Fournisseur) avec un volume 1.5x supérieur à la moyenne du rayon."`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static generateUserMessage(p: ProductAnalysisInput): string {
|
static generateUserMessage(p: ProductAnalysisInput): string {
|
||||||
const pmv = p.totalQuantite > 0 ? p.totalCa / p.totalQuantite : 0;
|
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 et contribution
|
// Contexte comparatif
|
||||||
const avgRef = p.avgQtyRayon || p.avgQtyFournisseur || 0;
|
const avgRef = p.avgQtyRayon || p.avgQtyFournisseur || 0;
|
||||||
const refName = p.avgQtyRayon ? "rayon" : "fournisseur";
|
const refName = p.avgQtyRayon ? "rayon" : "fournisseur";
|
||||||
const relativeVolume = avgRef > 0 ? ` (Moyenne du ${refName} : ${avgRef.toFixed(1)}u)` : "";
|
const relativeVolume = avgRef > 0 ? ` (Moyenne du ${refName} : ${avgRef.toFixed(1)}u)` : "";
|
||||||
|
|
||||||
|
// Poids Stratégique
|
||||||
const weights = p.shareCa !== undefined ? `
|
const weights = p.shareCa !== undefined ? `
|
||||||
CONTRIBUTION (%) :
|
CONTRIBUTION (%) :
|
||||||
- Chiffre d'Affaires : ${p.shareCa.toFixed(1)}% du total fournisseur
|
- Chiffre d'Affaires : ${p.shareCa.toFixed(1)}% du total fournisseur
|
||||||
- Marge Brute : ${p.shareMarge?.toFixed(1)}% du total fournisseur
|
- Marge Brute : ${p.shareMarge?.toFixed(1)}% du total fournisseur
|
||||||
- Volumes : ${p.shareQty?.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 totalContext = p.totalFournisseurCa ? `\nVALEUR TOTALE FOURNISSEUR : ${p.totalFournisseurCa.toFixed(0)}€ CA` : "";
|
||||||
|
|
||||||
const projectionInfo = p.regularityScore > 0 && p.regularityScore < 12 ? `
|
const projectionInfo = (p.regularityScore || 0) > 0 && (p.regularityScore || 0) < 12 ? `
|
||||||
--- ANALYSE DE POTENTIEL (Produit récent : ${p.regularityScore}/12 mois active) ---
|
--- ANALYSE DE POTENTIEL (Produit récent : ${p.regularityScore}/12 mois) ---
|
||||||
Projection 12 mois : ${Math.round(p.projectedTotalQuantite || 0)}u (${(p.projectedTotalCa || 0).toFixed(2)}€)` : "";
|
Projection 12 mois (Run Rate) : ${Math.round(p.projectedTotalQuantite || 0)}u (${(p.projectedTotalCa || 0).toFixed(2)}€)` : "";
|
||||||
|
|
||||||
const activityAlert = (p.inactivityMonths || 0) > 2
|
const activityAlert = (p.inactivityMonths || 0) > 2
|
||||||
? `\n⚠️ ALERTE : Inactif depuis ${p.inactivityMonths} mois`
|
? `\n⚠️ ALERTE INACTIVITÉ : ${p.inactivityMonths} mois sans vente.`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
return `Produit : ${p.libelle1} (${p.codein})
|
return `PRODUIT : ${p.libelle1} (${p.codein})
|
||||||
PERFORMANCE GLOBALE : ${p.score.toFixed(1)}/100
|
PERFORMANCE : Score ${p.score.toFixed(1)}/100 | Marge ${p.tauxMarge.toFixed(1)}% | PMV ${pmv.toFixed(2)}€
|
||||||
MARGE : ${p.tauxMarge.toFixed(1)}%
|
${volumeInfo}${relativeVolume}${weights}${totalContext}
|
||||||
PRIX MOYEN (PMV) : ${pmv.toFixed(2)}€
|
|
||||||
${volumeInfo}${relativeVolume}
|
|
||||||
${weights}
|
|
||||||
${totalContext}
|
|
||||||
|
|
||||||
Régularité : ${p.regularityScore}/12 mois active
|
CYCLE DE VIE : ${p.regularityScore}/12 mois active${projectionInfo}${activityAlert}
|
||||||
${projectionInfo}${activityAlert}
|
|
||||||
|
|
||||||
--- HISTORIQUE DES VENTES (12 mois) ---
|
--- HISTORIQUE MENSUEL PONDÉRÉ ---
|
||||||
${monthlySummary}
|
${monthlySummary}
|
||||||
|
|
||||||
Recommandation actuelle : ${p.codeGamme || "Aucune"}
|
Recommandation actuelle : ${p.codeGamme || "Aucune"}
|
||||||
Verdict attendu : [A], [C] ou [Z] avec justification par les chiffres.`;
|
Verdict attendu (A, C ou Z) ?`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static extractRecommendation(content: string): "A" | "C" | "Z" | null {
|
static extractRecommendation(content: string): "A" | "C" | "Z" | null {
|
||||||
|
|||||||
Reference in new issue
Block a user