chore: auto-merge develop → main
Triggered by: 75ca49a fix: smart shopping family suppression, shelf life pre-warming (v1.7.7)
This commit is contained in:
@@ -5,6 +5,12 @@ All notable changes to EverShelf will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.7.7] - 2026-05-10
|
||||
|
||||
### Fixed
|
||||
- **Smart shopping family suppression** — La logica `recentlyExhausted` (prodotti terminati < 14gg) bypassava erroneamente anche la suppression per `shopping_name` family, causando falsi positivi: prodotti come Yaourt Vanille apparivano come urgenti anche con 2kg di Yogurt in stock, Salame Paesano con 1kg di Affettato in stock, Gran bauletto rustico con più pani in stock. Ora `recentlyExhausted` bypassa solo il check token-based (match lasco), mentre la family suppression per `shopping_name` si applica sempre.
|
||||
- **Shelf life pre-warming nel cron** — Il cron ora chiama `prewarmShelfLifeCache()` ogni 5 minuti, precaricando via Gemini AI la shelf life degli item aperti in inventario (max 5 item per ciclo) prima che l'utente li visualizzi. Questo elimina il delay percepibile al primo click su "Aperto il...".
|
||||
|
||||
## [1.7.6] - 2026-05-10
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -66,6 +66,19 @@ try {
|
||||
echo '[' . date('Y-m-d H:i:s') . '] Bring! sync warning: ' . $be->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// ── Shelf life pre-warming ────────────────────────────────────────────
|
||||
// Pre-warm the opened shelf life cache for opened items not yet cached.
|
||||
// Capped at 5 items per cron cycle to avoid Gemini rate limits.
|
||||
try {
|
||||
$prewarmResult = prewarmShelfLifeCache($db, 5);
|
||||
if ($prewarmResult['warmed'] > 0) {
|
||||
echo '[' . date('Y-m-d H:i:s') . '] Shelf life pre-warm — warmed: ' . $prewarmResult['warmed']
|
||||
. ', skipped: ' . $prewarmResult['skipped'] . "\n";
|
||||
}
|
||||
} catch (Throwable $pe) {
|
||||
echo '[' . date('Y-m-d H:i:s') . '] Shelf life pre-warm warning: ' . $pe->getMessage() . "\n";
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
$msg = $e->getMessage();
|
||||
echo '[' . date('Y-m-d H:i:s') . '] ERROR: ' . $msg . "\n";
|
||||
|
||||
+39
-2
@@ -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;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "EverShelf",
|
||||
"short_name": "EverShelf",
|
||||
"description": "Gestione completa della dispensa di casa con scansione barcode",
|
||||
"version": "1.7.6",
|
||||
"version": "1.7.7",
|
||||
"start_url": "/evershelf/",
|
||||
"display": "standalone",
|
||||
"background_color": "#f0f4e8",
|
||||
|
||||
Reference in New Issue
Block a user