From df32fa3441e5b19b03c2be94c0e63cef8f731ace Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Sun, 29 Mar 2026 19:50:04 +0000 Subject: [PATCH] fix: spesa intelligente - filtro frequenza e recency per urgenze L'algoritmo ora calcola: - usesPerMonth: utilizzi al mese (non piu solo useCount totale) - daysSinceLastUse: giorni dall'ultimo utilizzo - isFrequent: >= 1.5 utilizzi/mese - isRegular: >= 0.5 utilizzi/mese (almeno 1 ogni 2 mesi) - isRecent: usato/comprato negli ultimi 60 giorni Nuova logica urgenze: - CRITICAL (esaurito): solo se frequente E recente - HIGH (esaurito): regolare E recente E storico consistente - Quasi finito/scorta bassa: solo per prodotti regolari/recenti - Previsione esaurimento: solo per frequenti E recenti - Prodotti usati raramente o non di recente vengono esclusi --- api/index.php | 53 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/api/index.php b/api/index.php index 3049617..3a0da9a 100644 --- a/api/index.php +++ b/api/index.php @@ -2306,6 +2306,18 @@ function smartShopping(PDO $db): void { // Cap daysLeft at a reasonable ceiling to avoid 999-day noise in reason strings $daysLeft = min($daysLeft, 365); + // --- Frequency & recency metrics --- + // Uses per month (30 days) — measures how frequently the product is actually used + $usesPerMonth = $daysSinceFirst >= 30 ? ($useCount / $daysSinceFirst) * 30 : $useCount; + // Days since last use/purchase — measures recency + $daysSinceLastUse = $lastOut ? ($now - $lastOut) / 86400 : ($lastIn ? ($now - $lastIn) / 86400 : 999); + // Is this a frequently used product? (≥ 1.5 uses/month) + $isFrequent = $usesPerMonth >= 1.5; + // Is it a regular product? (≥ 0.5 uses/month = at least once every 2 months) + $isRegular = $usesPerMonth >= 0.5; + // Is it recently relevant? (used/bought in last 60 days) + $isRecent = $daysSinceLastUse <= 60; + // --- Determine urgency --- $urgency = 'none'; // none, low, medium, high, critical $reasons = []; @@ -2313,39 +2325,50 @@ function smartShopping(PDO $db): void { // Out of stock if ($qty <= 0) { - if ($useCount >= 3 || $buyCount >= 2) { + if ($isFrequent && $isRecent) { + // Frequently used AND recently active → critical $urgency = 'critical'; $reasons[] = 'Esaurito'; $score += 100; if ($useCount >= 5) { $score += 20; $reasons[] = "Uso frequente ({$useCount}x)"; } + } elseif ($isRegular && $isRecent && ($useCount >= 4 || $buyCount >= 3)) { + // Regularly used, recently active → high + $urgency = 'high'; + $reasons[] = 'Esaurito'; + $score += 70; + } elseif ($isRecent && $buyCount >= 2) { + // At least bought a couple times recently → low + $urgency = 'low'; + $reasons[] = 'Esaurito'; + $score += 30; } else { - // Rarely used — not urgent even if finished + // Rarely used or not used recently — skip continue; } } - // Almost finished - if ($qty > 0 && $pctLeft <= 15) { - $urgency = 'high'; + // Almost finished — only flag if usage frequency justifies it + if ($qty > 0 && $pctLeft <= 15 && $isRegular) { + $urgency = $isFrequent ? 'high' : 'medium'; $reasons[] = 'Quasi finito (' . round($pctLeft) . '%)'; $score += 80; - } elseif ($qty > 0 && $pctLeft <= 30) { - if ($dailyRate > 0 && $daysLeft <= 5) { + } elseif ($qty > 0 && $pctLeft <= 30 && $isRegular) { + if ($dailyRate > 0 && $daysLeft <= 5 && $isFrequent) { $urgency = 'high'; $reasons[] = 'Finisce tra ~' . round($daysLeft) . 'gg'; $score += 75; - } elseif ($dailyRate > 0 && $daysLeft <= 10) { + } elseif ($dailyRate > 0 && $daysLeft <= 10 && $isRecent) { $urgency = 'medium'; $reasons[] = 'Finisce tra ~' . round($daysLeft) . 'gg'; $score += 50; - } else { + } elseif ($isRecent) { $urgency = 'low'; $reasons[] = 'Scorta bassa (' . round($pctLeft) . '%)'; $score += 30; } } - // Expiring soon or expired (needs replacement) + // Expiring soon or expired (needs replacement) — valid regardless of frequency if ($isExpired && $qty > 0) { $urgency = 'critical'; $reasons[] = 'Scaduto!'; @@ -2356,15 +2379,15 @@ function smartShopping(PDO $db): void { $score += 40; } - // Frequently used but stock getting low (predictive) - if ($urgency === 'none' && $dailyRate > 0 && $daysLeft <= 14 && $useCount >= 3) { + // Frequently used but stock getting low (predictive) — stricter thresholds + if ($urgency === 'none' && $dailyRate > 0 && $daysLeft <= 14 && $isFrequent && $isRecent) { $urgency = 'low'; $reasons[] = 'Previsto esaurimento tra ~' . round($daysLeft) . 'gg'; $score += 25; } - // Opened item with fast consumption - if ($isOpened && $urgency === 'none' && $dailyRate > 0 && $daysLeft <= 7) { + // Opened item with fast consumption — only if actually used regularly + if ($isOpened && $urgency === 'none' && $dailyRate > 0 && $daysLeft <= 7 && $isRegular) { $urgency = 'low'; $reasons[] = 'Aperto, finisce presto'; $score += 20; @@ -2392,6 +2415,8 @@ function smartShopping(PDO $db): void { 'use_count' => $useCount, 'buy_count' => $buyCount, 'daily_rate' => round($dailyRate, 2), + 'uses_per_month' => round($usesPerMonth, 1), + 'days_since_last_use' => round($daysSinceLastUse), 'days_left' => round($daysLeft), 'expiry_date' => $expiryDate, 'days_to_expiry' => round($daysToExpiry),