fix: smart shopping family suppression, shelf life pre-warming (v1.7.7)

- Remove recentlyExhausted bypass from shopping_name family suppression:
  products recently exhausted (<14d) were incorrectly flagged as critical
  even when the same family had ample stock (Yogurt 2002g, Affettato 1022g,
  Pane 400g). recentlyExhausted now only bypasses loose token-based coverage.
- Add prewarmShelfLifeCache() in cron: pre-warms opened shelf life via
  Gemini AI (max 5 items/cycle) so the UI never blocks on first load.
This commit is contained in:
dadaloop82
2026-05-10 13:18:41 +00:00
parent ed447d5811
commit 75ca49ac4e
4 changed files with 59 additions and 3 deletions
+39 -2
View File
@@ -2386,6 +2386,42 @@ function callGeminiWithFallback(string $apiKey, array $payload, int $timeout = 3
// ===== AI-POWERED OPENED SHELF LIFE =====
/**
* Cron helper: pre-warm the opened shelf life cache for opened inventory items that
* have no cache entry yet. Called once per cron cycle; capped to $limit items to
* avoid blocking or hitting Gemini rate limits.
* Returns ['warmed' => int, 'skipped' => int].
*/
function prewarmShelfLifeCache(PDO $db, int $limit = 5): array {
$cacheFile = __DIR__ . '/../data/opened_shelf_cache.json';
$cache = [];
if (file_exists($cacheFile)) {
$cache = json_decode(file_get_contents($cacheFile), true) ?: [];
}
// Fetch opened items from inventory (only those still with quantity > 0)
$rows = $db->query("
SELECT p.name, p.category, i.location
FROM inventory i
JOIN products p ON p.id = i.product_id
WHERE i.opened_at IS NOT NULL AND i.quantity > 0
ORDER BY i.opened_at ASC
")->fetchAll(PDO::FETCH_ASSOC);
$warmed = 0;
$skipped = 0;
foreach ($rows as $row) {
if ($warmed >= $limit) { $skipped++; continue; }
$cacheKey = md5(mb_strtolower($row['name']) . '|' . mb_strtolower($row['location']));
if (isset($cache[$cacheKey])) { $skipped++; continue; }
// Call with AI enabled — this writes to cache internally
getOpenedShelfLifeDays($row['name'], $row['category'] ?? '', $row['location'], false, true);
$warmed++;
}
return ['warmed' => $warmed, 'skipped' => $skipped];
}
/**
* Return the number of days a product remains safe after opening, depending on storage location.
* Checks a local JSON cache first (keyed by product name+location); on cache miss, asks Gemini AI.
@@ -5655,8 +5691,9 @@ function smartShopping(PDO $db): void {
// (e.g. "Formaggio") and there's stock of ANY product with the same generic name,
// the need is covered. This catches "Bel Paese" → covered by "Formaggio Gouda" in stock,
// "Biscotti Pastefrolle" → covered by "Frollini..." (both shopping_name="Biscotti"), etc.
// Exception: recently exhausted products (< 14 days) skip this suppression too.
if (!$coveredByEquivalent && !$recentlyExhausted) {
// NOTE: recentlyExhausted does NOT bypass this check — same-family stock always suppresses.
// recentlyExhausted only bypasses the loose token-based check above.
if (!$coveredByEquivalent) {
$sName = strtolower(trim($p['shopping_name'] ?? ''));
if ($sName !== '' && ($stockByShoppingName[$sName] ?? 0) > 0) {
$coveredByEquivalent = true;