feat: Implement v3 context profiler for multi-store product normalization and an AI analysis engine.

This commit is contained in:
Michael committed 2026-03-11 16:41:01 +01:00
1 parent cfbf293104
commit 49e1dd9d9b
3 files changed
+30 -23

No files matched your search

@@ -17,7 +17,7 @@ export class AnalysisEngine {
// -----------------------------------------------------------------------
static generateSystemPrompt(): string {
return `Tu es Mary, Senior Retail Strategist. Tu analyses des produits pour recommander A (garder) ou Z (sortir).
return `Tu es Mary, Senior Retail Strategist pour une enseigne discount d'équipement de la maison (type La Foir'Fouille). Ton modèle économique repose sur la rotation rapide, le volume, et la rentabilité de l'espace en rayon. Tu analyses des produits pour recommander A (garder) ou Z (sortir).
IMPORTANT : La gamme C est RÉSERVÉE aux produits saisonniers et gérée MANUELLEMENT par l'acheteur. Tu ne dois JAMAIS recommander C.
--- ORDRE DE PRIORITÉ (STRICT) ---
@@ -25,41 +25,38 @@ IMPORTANT : La gamme C est RÉSERVÉE aux produits saisonniers et gérée MANUEL
1. GARDE-FOU PROTECTION : Si isProtected = true (Nouveauté / Dernier Produit / Top30), c'est un signal FORT en faveur de A.
Cependant, si les performances réelles du produit sont clairement insuffisantes (CA dérisoire, marge nulle ou négative), tu PEUX passer outre la protection et recommander Z avec justification.
2. SEUIL PLANCHER ABSOLU (règle critique) :
Si isLowContribution = true [percentile CA <= 30 ET percentile QTÉ <= 30 du fournisseur]
ET score composite < 35
ET scoreCritique = true [score brut < 20]
→ Z DIRECT. Ce produit est marginal et sous-performant.
2. SEUIL PLANCHER ABSOLU ET CONTEXTE DISCOUNT (règle critique) :
- Règle A : Si isLowContribution = true [percentile CA <= 30 ET percentile QTÉ <= 30] ET score composite < 35 ET scoreCritique = true → Z DIRECT.
- Règle B (Morts-vivants de rayon) : Si les statistiques ABSOLUES sont dérisoires pour notre modèle discount (ex: CA annuel de quelques dizaines d'euros OU Quantité annuelle vendue de quelques unités, ex: < 20), et ce MÊME SI les percentiles sont élevés (car le lot est mauvais) → Z DIRECT. Un produit qui vend 5 unités par an n'est pas rentable pour un grand magasin.
3. RÈGLE MANAGER : Si le manager a défini une consigne ET que le produit est concerné → Appliquer la consigne À LA LETTRE.
Si la règle ordonne explicitement une Gamme B, C ou D, TU DOIS SORTIR "B", "C" ou "D" dans la recommandation JSON. C'est la SEULE exception où tu es autorisé à utiliser B, C ou D. "rule_applies" doit être 'true'.
Si la règle ordonne explicitement une Gamme B, C ou D, TU DOIS SORTIR "B", "C" ou "D". "rule_applies" doit être 'true'.
4. ANALYSE CONTEXTUELLE (si aucune règle ci-dessus ne s'applique) :
Utilise les données de la fiche pour raisonner. Les quadrants sont des INDICES, pas des verdicts. Par défaut, génère "A" ou "Z".
Les quadrants sont des INDICES, pas des verdicts.
— Quadrant STAR ⭐ : Fort signal positif → A sauf inactivité ≥ 3 mois.
— Quadrant TRAFIC 🚶 : Générateur de flux local.
Si percentileQty >= 60 → A (bon volume relatif).
Si percentileQty < 40 ET poids CA fournisseur < 0.5% ET score < 35 → Z (marginal ET sous-performant).
Sinon → A si actif (inactivité < 3 mois), Z si inactif ≥ 3 mois.
— Quadrant MARGE 💎 : Ce produit contribue par la rentabilité, pas le volume.
Si percentileMarge >= 70 → A (capital rentabilité du lot).
Si percentileMarge < 70 ET poids CA fournisseur < 0.5% ET score < 35 → Z (marginal ET sous-performant).
Sinon → A si actif (inactivité < 3 mois), Z si inactif ≥ 3 mois.
— Quadrant WATCH ⚠️ : Signal négatif.
Si poids CA rayon > 5% ou poids QTÉ rayon > 5% → A (surveiller).
Si percentileCa >= 80 ou percentileQty >= 80 → A (produit majeur du lot, vital).
Sinon → Z si inactivité ≥ 2 mois, Z systématiquement si CA/QTÉ faibles, A uniquement si fort justificatif.
— Quadrant TRAFIC 🚶 : Générateur de flux.
Si percentileQty >= 60 → A.
Si percentileQty < 40 ET score < 35 → Z.
Sinon → A si actif, Z si inactif ≥ 3 mois.
— Quadrant MARGE 💎 : Contributeur rentabilité.
Si percentileMarge >= 70 → A.
Si percentileMarge < 70 ET score < 35 → Z.
Sinon → A si actif, Z si inactif ≥ 3 mois.
— Quadrant WATCH ⚠️ : Sous-performant.
Si poids CA rayon > 5% ou poids QTÉ rayon > 5% → A.
Si percentileCa >= 80 ou percentileQty >= 80 → A.
Sinon → Z systématiquement pour purger le rayon des faibles rotations.
--- COHÉRENCE INTER-PRODUITS ---
Ne mets jamais Z un produit si son percentile CA ET son percentile QTÉ sont tous les deux supérieurs à un autre produit déjà classé A dans ce lot.
Ne mets jamais Z un produit si son percentile CA ET son percentile QTÉ sont tous les deux supérieurs à un autre produit classé A.
--- FORMAT OBLIGATOIRE ---
JSON uniquement, sans markdown.
{
"rule_applies": boolean,
"recommendation": "A" | "B" | "C" | "D" | "Z",
"justification": "2 phrases max. Cite poids, percentile, quadrant, score."
"justification": "2 phrases max. Cite les chiffres absolus si ridicules, ou les percentiles/quadrant."
}`;
}
@@ -116,6 +113,9 @@ JSON uniquement, sans markdown.
lines.push(`--- SIGNAUX ---`);
// Signal rouge prioritaire
if (ctx.isDeadStock) {
lines.push(`[⛔ STOCK MORT] Statistiques absolues dérisoires (CA < 100€ ou QTÉ < 20 unités sur le réseau) → non rentable pour notre modèle volume`);
}
if (ctx.isLowContribution) {
lines.push(`[⛔ CONTRIBUTION FAIBLE] Percentiles CA (${ctx.percentileCa}) et QTÉ (${ctx.percentileQty}) dans les 30% inférieurs → produit marginal`);
}
@@ -115,6 +115,10 @@ export interface ProductContextProfile {
* Le produit appartient aux 30% les moins performants en CA et Quantité.
*/
isLowContribution: boolean;
/**
* Produit avec des statistiques absolues dérisoires (ex: < 100€ CA ou < 20 unités au réseau)
*/
isDeadStock: boolean;
// Gardes-fous (issus du ScoringEngine)
isProtected: boolean;
@@ -295,6 +299,7 @@ export class ContextProfiler {
: 0;
const isLowContribution = pCa <= 30 && pQty <= 30 && pMarge <= 70;
const isDeadStock = (target.totalCa ?? 0) < 100 || (target.totalQuantite ?? 0) < 20;
// 6. Signaux Trafic / Marge + Quadrant
const signalsActive = rayonSizeForSignals >= MIN_RAYON_SIZE;
@@ -378,6 +383,7 @@ export class ContextProfiler {
isHighVolumeWithLowMargin,
isMargePure,
isLowContribution,
isDeadStock,
isProtected,
protectionReason,
+1
View File
@@ -32,3 +32,4 @@ Chargement de la configuration et présentation du menu des actions de l'agent B
- Moteur de Score et analyse IA : Explication détaillée des mécaniques de `scoring-engine` (Percentiles, pondération réseau, quadrants) et d'`analysis-engine` (Prompt v4, gardes-fous, règles manager) présentée à l'utilisateur par l'équipe (Mary et Winston).
- Correction de l'anomalie du Score "100" (ex: LOT DE 4 BUBBLES DINO) : Remplacement de la méthode "MAX" dans `score-engine.ts` par une "moyenne pondérée" réaliste (45% CA, 35% Volume, 20% Marge).
- Durcissement des règles de l'IA (`analysis-engine.ts`) : Un produit dans le Quadrant "WATCH" ne peut désormais être sauvé en "A" que s'il se trouve dans les 20% supérieurs (percentile ≥ 80) du lot, et non plus simplement > 50%.
- Contexte Discount Absolu (La Foir'Fouille) : Ajout d'une protection en cas de statistiques absolues dérisoires (ex: < 20 ventes/an, CA < 100€) pour éviter qu'un produit soit gardé "A" uniquement sur la base de bons "percentiles" dans un lot de faible qualité. Le flag `isDeadStock` a été ajouté au `context-profiler` et remonté à l'IA avec un signal "⛔ STOCK MORT" strict, la forçant à générer "Z" pour les morts-vivants du rayon.