feat: introduce AI analysis engine with detailed system prompt and rich contextual product data messaging for recommendations.

This commit is contained in:
Michael committed 2026-02-27 11:46:25 +01:00
1 parent 5773ed9a26
commit 8e07e8c115
2 files changed
+28 -14

No files matched your search

@@ -38,8 +38,10 @@ IMPORTANT : La gamme C est RÉSERVÉE aux produits saisonniers et gérée MANUEL
— Quadrant STAR ⭐ : Fort signal positif → A sauf inactivité ≥ 3 mois.
— Quadrant TRAFIC 🚶 : Signal positif SEULEMENT si poids QTÉ fournisseur > 1%.
Si poids < 1% et score < 40 → Z.
— Quadrant MARGE 💎 : Signal positif SEULEMENT si poids CA fournisseur > 0.5%.
Si poids < 0.5% et score < 35 → Z.
— Quadrant MARGE 💎 : Ce produit contribue par la rentabilité, pas le volume. C'est son rôle attendu.
Si percentileMarge >= 70 → A (capital rentabilité du lot, protéger absolument).
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).
Sinon → Z si inactivité ≥ 2 mois, A si actif.
@@ -223,33 +223,45 @@ export class ContextProfiler {
? Math.round((getWeightedQty(target) / totalQtyFournisseur) * 1000) / 10
: 0;
const isLowContribution = weightCaFournisseur < 0.5 && weightQtyFournisseur < 0.5;
// Bug fix : un produit dans le top 30% de marge du lot apporte une valeur
// réelle en rentabilité. L'exclure du signal isLowContribution évite de le
// pénaliser uniquement parce que son volume/CA est faible (profil 💎 pur).
const isLowContribution = weightCaFournisseur < 0.5 && weightQtyFournisseur < 0.5 && pMarge <= 70;
// 6. Signaux Trafic / Marge + Quadrant (sur valeurs PAR MAGASIN)
// Médianes calculées ici pour être disponibles dans les sections 6 ET 7.
const medianQtyPerStore = computeMedian(allQtyPerStore);
const medianMarge = computeMedian(allMargeValues);
// 6. Signaux Trafic / Marge (sur valeurs PAR MAGASIN)
const rayonSizeForSignals = rayonProds.length;
const signalsActive = rayonSizeForSignals >= MIN_RAYON_SIZE;
let isHighVolumeWithLowMargin = false;
let isMargePure = false;
if (signalsActive) {
// Bug fix : isMargePure est désormais calculé sur tous les rayons, même petits.
// Raison : la marge absolue est une donnée fiable quelle que soit la taille du lot.
// Le signal isHighVolumeWithLowMargin (trafic) garde le seuil MIN_RAYON_SIZE car
// il nécessite une distribution volumique suffisante pour être statistiquement valide.
{
const marge40 = valueAtPercentile(allMargeValues, 40);
const marge70 = valueAtPercentile(allMargeValues, 70);
const qty60PerStore = valueAtPercentile(allQtyPerStore, 60);
const medianQtyPerStore = computeMedian(allQtyPerStore);
isHighVolumeWithLowMargin =
targetQtyPerStore >= qty60PerStore &&
(target.tauxMarge ?? 0) < marge40;
// Signal trafic : nécessite un rayon suffisamment large
if (signalsActive) {
isHighVolumeWithLowMargin =
targetQtyPerStore >= qty60PerStore &&
(target.tauxMarge ?? 0) < marge40;
}
// Signal marge pure : au-dessus de la médiane marge + volume faible.
// Utilise la médiane (P50) plutôt que P70 pour être robuste sur petits lots.
isMargePure =
(target.tauxMarge ?? 0) >= marge70 &&
(target.tauxMarge ?? 0) > medianMarge &&
targetQtyPerStore < medianQtyPerStore;
}
// 7. Quadrant (basé sur les médianes PAR MAGASIN)
const medianQtyPerStore = computeMedian(allQtyPerStore);
const medianMarge = computeMedian(allMargeValues);
// 7. Quadrant (basé sur les médianes PAR MAGASIN — déjà calculées en section 6)
const { quadrant, quadrantLabel, quadrantEmoji } = ContextProfiler.resolveQuadrant(
targetQtyPerStore,
target.tauxMarge ?? 0,