fix: force score=0 for zero-sale products and fix reception SQL sign

- score-engine: add hard guard (wQty=0 && wCa=0 → score=0) before any
  calculation to prevent edge cases (residual margin, percentile rank)
  from producing non-zero scores on unsold articles
- score-engine: set inactivity=24 when product has never sold, so the
  regularityMultiplier penalty (0.3) is correctly applied instead of 1
- pg-ff-client: remove erroneous negation on qte_recue — entry movements
  have positive qtemvt in the DB, negating them produced negative values
  that failed the > 0 check, hiding all entrées in tooltips and modals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Sonnet 4.6 committed 2026-03-25 09:19:57 +01:00
1 parent 76b06419a0
commit 7a30783285
1 file changed
+22
+22
View File
@@ -115,6 +115,9 @@ export function computeProductScores(rows: ProductRow[]): ProductRow[] {
const lastY = parseInt(lastSaleMonth.substring(0, 4));
const lastM = parseInt(lastSaleMonth.substring(4, 6));
inactivity = (refY - lastY) * 12 + (refM - lastM);
} else if (refMonth && !lastSaleMonth) {
// Jamais vendu sur la fenêtre → inactivité maximale
inactivity = 24;
}
return {
@@ -158,6 +161,25 @@ export function computeProductScores(rows: ProductRow[]): ProductRow[] {
const d = weightedData[idx];
const r = d.row;
// --- Guard absolu : aucune vente → score 0, quelle que soit la marge ou le ranking ---
if (d.wQty === 0 && d.wCa === 0) {
r.avgQtyFournisseur = avgQty;
r.totalFournisseurCa = totalCa;
r.shareQty = 0;
r.shareCa = 0;
r.shareMarge = 0;
const r2z = r.code2 || "default";
const rStat = rayonTotals.get(r2z);
r.avgQtyRayon = rStat ? rStat.total / rStat.count : 0;
r.unitsPerStorePerMonth = 0;
r.caPerStorePerYear = 0;
r.score = 0;
r.isRecent = true;
r.isLastProduct = rows.length === 1;
r.isTop30Supplier = false;
continue;
}
// --- Métriques de contexte (inchangées, pour l'IA et l'affichage) ---
r.avgQtyFournisseur = avgQty;
r.totalFournisseurCa = totalCa;