diff --git a/api/index.php b/api/index.php
index be1561f..e0a1024 100644
--- a/api/index.php
+++ b/api/index.php
@@ -667,13 +667,45 @@ function addToInventory(PDO $db): void {
$bringKey = italianToBring($prodName);
$listData = bringRequest('GET', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}");
if ($listData && isset($listData['purchase'])) {
+ // Token-based matching — same logic as _productOnBring() in smart_shopping
+ $stop = ['di','del','della','dei','degli','dalle','delle','da','in','con','per',
+ 'a','e','il','lo','la','i','gli','le','un','uno','una','al','alle','agli','allo'];
+ $tokenize = function(string $s) use ($stop): array {
+ $clean = mb_strtolower(preg_replace('/[^\p{L}\s]/u', ' ', $s));
+ return array_values(array_filter(
+ preg_split('/\s+/', trim($clean)),
+ fn($t) => mb_strlen($t) > 2 && !in_array($t, $stop)
+ ));
+ };
+ $prodTokens = $tokenize($prodName);
+ $keyTokens = $tokenize($bringKey);
+ $prodFirst = $prodTokens[0] ?? '';
+ $keyFirst = $keyTokens[0] ?? '';
foreach ($listData['purchase'] as $item) {
- if (strcasecmp($item['name'] ?? '', $bringKey) === 0) {
- $body = http_build_query(['uuid' => $listUUID, 'remove' => $bringKey]);
- bringRequest('PUT', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}", $body);
+ $rawName = $item['name'] ?? '';
+ // 1. Exact match on translated catalog key or original Italian name
+ if (strcasecmp($rawName, $bringKey) === 0 || strcasecmp($rawName, $prodName) === 0) {
+ bringRequest('PUT', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}",
+ http_build_query(['uuid' => $listUUID, 'remove' => $rawName]));
$removedFromBring = true;
break;
}
+ // 2. Token-based fuzzy: first significant word must match
+ if ($prodFirst || $keyFirst) {
+ $rawTokens = $tokenize($rawName);
+ $rawFirst = $rawTokens[0] ?? '';
+ if ($rawFirst && (
+ $rawFirst === $prodFirst ||
+ $rawFirst === $keyFirst ||
+ in_array($prodFirst, $rawTokens) ||
+ in_array($keyFirst, $rawTokens)
+ )) {
+ bringRequest('PUT', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}",
+ http_build_query(['uuid' => $listUUID, 'remove' => $rawName]));
+ $removedFromBring = true;
+ break;
+ }
+ }
}
}
}
diff --git a/assets/css/style.css b/assets/css/style.css
index b1e32e3..e769989 100644
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -2671,6 +2671,47 @@ body {
font-weight: 600;
}
+/* ===== MULTI-BATCH EXPIRY (conf products with different expiry dates) ===== */
+.multi-batch-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: 8px;
+ padding: 8px 10px;
+ background: rgba(0,0,0,0.04);
+ border: 1px dashed var(--border);
+ border-radius: var(--radius-sm);
+}
+
+.multi-batch-qty {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ flex-shrink: 0;
+}
+
+.multi-batch-unit {
+ font-size: 0.8rem;
+ color: var(--text-muted);
+}
+
+.multi-batch-date {
+ flex: 1;
+ min-width: 0;
+ font-size: 0.85rem !important;
+}
+
+.btn-icon-sm {
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 4px 8px;
+ font-size: 0.75rem;
+ cursor: pointer;
+ color: var(--text-muted);
+ flex-shrink: 0;
+}
+
/* ===== PRODUCT DETAILS CARD (Action Page) ===== */
.product-details-card {
background: var(--bg-card);
@@ -3489,6 +3530,32 @@ body {
min-width: 0;
}
+.cooking-ing-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+ margin-top: 5px;
+}
+
+.cooking-ing-chip {
+ font-size: 0.72rem;
+ background: rgba(255,255,255,0.12);
+ border-radius: 5px;
+ padding: 2px 7px;
+ color: rgba(255,255,255,0.75);
+ white-space: nowrap;
+}
+
+.cooking-ing-chip.exp-soon {
+ background: rgba(239,68,68,0.3);
+ color: #fca5a5;
+}
+
+.cooking-ing-chip.exp-close {
+ background: rgba(234,179,8,0.3);
+ color: #fde047;
+}
+
.cooking-use-btn {
flex-shrink: 0;
background: #16a34a;
diff --git a/assets/js/app.js b/assets/js/app.js
index 22b81b3..e0ce1c9 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -3269,6 +3269,8 @@ function showAddForm() {
// Reset historical expiry for this product; will be fetched async
window._historyExpiryDays = null;
window._historyExpiryCount = 0;
+ // Reset extra batches from previous add
+ window._addExtraBatches = [];
// Store base expiry for vacuum recalculation
window._addBaseExpiryDays = estimatedDays;
@@ -3293,6 +3295,12 @@ function showAddForm() {
📝 Puoi modificare la data o scansionarla con la fotocamera
+
+
+
+ 📦 + Lotto con scadenza diversa
+
+
`;
showPage('add');
@@ -3406,6 +3414,10 @@ function onAddUnitChange() {
// Scroll into view so the user sees the new field
if (isConf) setTimeout(() => confRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' }), 100);
}
+
+ // Show/hide multi-batch section (only for conf unit)
+ const mbSection = document.getElementById('multi-batch-section');
+ if (mbSection) mbSection.style.display = unit === 'conf' ? 'block' : 'none';
// If switching units, suggest a sensible quantity
// BUT only if the user hasn't manually changed the quantity in this form
@@ -3457,6 +3469,11 @@ function selectPurchaseType(btn, type) {
btn.parentElement.querySelectorAll('.purchase-type-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
+ // Reset extra batches when switching purchase type
+ window._addExtraBatches = [];
+ const mbContainer = document.getElementById('multi-batch-container');
+ if (mbContainer) mbContainer.innerHTML = '';
+
const detailDiv = document.getElementById('expiry-detail');
// Save current quantity before switching, so we can preserve it
@@ -3489,6 +3506,9 @@ function selectPurchaseType(btn, type) {
`;
// Restore quantity - switching purchase type should NOT change it
document.getElementById('add-quantity').value = currentQty;
+ // Show multi-batch section only in "new" mode (and only for conf unit)
+ const mbSection = document.getElementById('multi-batch-section');
+ if (mbSection) mbSection.style.display = (document.getElementById('add-unit')?.value === 'conf') ? 'block' : 'none';
} else {
detailDiv.innerHTML = `
@@ -3511,6 +3531,9 @@ function selectPurchaseType(btn, type) {
`;
// DON'T auto-set remaining percentage - keep the quantity the user already entered
+ // Hide multi-batch section in "existing" mode
+ const mbSection = document.getElementById('multi-batch-section');
+ if (mbSection) mbSection.style.display = 'none';
}
}
@@ -3528,6 +3551,50 @@ function setRemainingPct(pct) {
document.getElementById('add-quantity').value = adjustedQty;
}
+// ===== MULTI-EXPIRY BATCHES (for conf products with different expiry dates) =====
+window._addExtraBatches = [];
+
+function addExpiryBatch() {
+ const loc = document.getElementById('add-location')?.value || '';
+ const baseDays = window._historyExpiryDays ?? estimateExpiryDays(currentProduct, loc);
+ const estimatedDate = addDays(baseDays);
+ window._addExtraBatches.push({ qty: 1, expiry: estimatedDate });
+ _rebuildMultiBatchUI();
+}
+
+function removeExpiryBatch(i) {
+ window._addExtraBatches.splice(i, 1);
+ _rebuildMultiBatchUI();
+}
+
+function adjustBatchQty(i, delta) {
+ window._addExtraBatches[i].qty = Math.max(1, (window._addExtraBatches[i].qty || 1) + delta);
+ _rebuildMultiBatchUI();
+}
+
+function _rebuildMultiBatchUI() {
+ const container = document.getElementById('multi-batch-container');
+ if (!container) return;
+ if (window._addExtraBatches.length === 0) {
+ container.innerHTML = '';
+ return;
+ }
+ container.innerHTML = window._addExtraBatches.map((b, i) => `
+
+ `).join('');
+}
+
function selectLocation(btn, loc) {
btn.parentElement.querySelectorAll('.loc-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
@@ -3583,8 +3650,46 @@ async function submitAdd(e) {
showToast(`✅ ${currentProduct.name} aggiunto!${qtyInfo}`, 'success');
if (result.removed_from_bring) {
setTimeout(() => showToast('🛒 Rimosso dalla lista della spesa', 'info'), 1500);
+ } else if (shoppingItems.length > 0 && shoppingListUUID) {
+ // PHP matching may have missed the item (custom name / no catalog match) —
+ // try a client-side fuzzy remove using the already-loaded shoppingItems
+ const match = _findSimilarItem(currentProduct.name, shoppingItems);
+ if (match) {
+ api('bring_remove', {}, 'POST', {
+ name: match.name,
+ rawName: match.rawName || '',
+ listUUID: shoppingListUUID
+ }).then(r => {
+ if (r && r.success) {
+ shoppingItems = shoppingItems.filter(i => i !== match);
+ setTimeout(() => showToast('🛒 Rimosso dalla lista della spesa', 'info'), 1500);
+ }
+ }).catch(() => {});
+ }
}
if (!spesaModeAfterAdd()) showPage('dashboard');
+
+ // Submit extra batches (different expiry dates) in the background, silently
+ if ((window._addExtraBatches || []).length > 0) {
+ const loc = document.getElementById('add-location')?.value || result.location || 'dispensa';
+ const selectedUnit = document.getElementById('add-unit').value;
+ const productUnit = currentProduct.unit || 'pz';
+ const confUnit = document.getElementById('add-conf-unit')?.value || null;
+ const confSize = parseFloat(document.getElementById('add-conf-size')?.value) || null;
+ for (const batch of window._addExtraBatches) {
+ if (!batch.qty || batch.qty <= 0) continue;
+ api('inventory_add', {}, 'POST', {
+ product_id: currentProduct.id,
+ quantity: batch.qty,
+ location: loc,
+ expiry_date: batch.expiry || null,
+ unit: selectedUnit !== productUnit ? selectedUnit : null,
+ package_unit: selectedUnit === 'conf' ? confUnit : null,
+ package_size: selectedUnit === 'conf' ? confSize : null,
+ }).catch(() => {});
+ }
+ window._addExtraBatches = [];
+ }
} else {
showToast(result.error || 'Errore', 'error');
}
@@ -6911,11 +7016,25 @@ function renderCookingStep() {
const ingsEl = document.getElementById('cooking-step-ings');
if (ings.length > 0) {
+ const LOC_LABELS = { dispensa: '🏠 Dispensa', frigo: '❄️ Frigo', freezer: '🧊 Freezer' };
ingsEl.innerHTML = ings.map(ing => {
const loc = (ing.location || 'dispensa').replace(/'/g, "\\'");
const qtyNum = ing.qty_number || 0;
+ // Build info chips: brand, location, expiry
+ const chips = [];
+ if (ing.brand) chips.push(`${escapeHtml(ing.brand)} `);
+ const locLabel = LOC_LABELS[ing.location] || (ing.location ? `📍 ${ing.location}` : '🏠 Dispensa');
+ chips.push(`${locLabel} `);
+ if (ing.expiry_date) {
+ const daysLeft = Math.round((new Date(ing.expiry_date) - new Date()) / 86400000);
+ const expClass = daysLeft <= 3 ? 'exp-soon' : daysLeft <= 7 ? 'exp-close' : '';
+ chips.push(`📅 scade ${formatDate(ing.expiry_date)} `);
+ }
return `
-
📦 ${escapeHtml(ing.name)} : ${escapeHtml(ing.qty)}
+
+
📦 ${escapeHtml(ing.name)} : ${escapeHtml(ing.qty)}
+
${chips.join('')}
+
📤 Usa
`;
}).join('');
diff --git a/data/cron.log b/data/cron.log
index 89cd6e4..b724db9 100644
--- a/data/cron.log
+++ b/data/cron.log
@@ -1474,3 +1474,331 @@
[2026-04-06 08:40:02] OK — 13 items cached
[2026-04-06 08:45:02] OK — 13 items cached
[2026-04-06 08:50:01] OK — 13 items cached
+[2026-04-06 08:55:02] OK — 12 items cached
+[2026-04-06 09:00:02] OK — 13 items cached
+[2026-04-06 09:05:01] OK — 13 items cached
+[2026-04-06 09:10:02] OK — 13 items cached
+[2026-04-06 09:15:02] OK — 12 items cached
+[2026-04-06 09:20:01] OK — 12 items cached
+[2026-04-06 09:25:02] OK — 12 items cached
+[2026-04-06 09:30:02] OK — 12 items cached
+[2026-04-06 09:35:01] OK — 12 items cached
+[2026-04-06 09:40:02] OK — 12 items cached
+[2026-04-06 09:45:02] OK — 12 items cached
+[2026-04-06 09:50:01] OK — 12 items cached
+[2026-04-06 09:55:02] OK — 12 items cached
+[2026-04-06 10:00:02] OK — 12 items cached
+[2026-04-06 10:05:01] OK — 12 items cached
+[2026-04-06 10:10:02] OK — 12 items cached
+[2026-04-06 10:15:02] OK — 12 items cached
+[2026-04-06 10:20:02] OK — 12 items cached
+[2026-04-06 10:25:01] OK — 12 items cached
+[2026-04-06 10:30:02] OK — 11 items cached
+[2026-04-06 10:35:02] OK — 11 items cached
+[2026-04-06 10:40:01] OK — 11 items cached
+[2026-04-06 10:45:02] OK — 29 items cached
+[2026-04-06 10:50:02] OK — 29 items cached
+[2026-04-06 10:55:01] OK — 29 items cached
+[2026-04-06 11:00:02] OK — 29 items cached
+[2026-04-06 11:05:02] OK — 29 items cached
+[2026-04-06 11:10:01] OK — 29 items cached
+[2026-04-06 11:15:03] OK — 29 items cached
+[2026-04-06 11:20:01] OK — 29 items cached
+[2026-04-06 11:25:02] OK — 29 items cached
+[2026-04-06 11:30:02] OK — 29 items cached
+[2026-04-06 11:35:01] OK — 29 items cached
+[2026-04-06 11:40:02] OK — 29 items cached
+[2026-04-06 11:45:02] OK — 29 items cached
+[2026-04-06 11:50:01] OK — 29 items cached
+[2026-04-06 11:55:02] OK — 29 items cached
+[2026-04-06 12:00:02] OK — 29 items cached
+[2026-04-06 12:05:02] OK — 29 items cached
+[2026-04-06 12:10:01] OK — 29 items cached
+[2026-04-06 12:15:02] OK — 29 items cached
+[2026-04-06 12:20:02] OK — 29 items cached
+[2026-04-06 12:25:02] OK — 29 items cached
+[2026-04-06 12:30:01] OK — 29 items cached
+[2026-04-06 12:35:02] OK — 29 items cached
+[2026-04-06 12:40:01] OK — 29 items cached
+[2026-04-06 12:45:02] OK — 29 items cached
+[2026-04-06 12:50:02] OK — 29 items cached
+[2026-04-06 12:55:01] OK — 29 items cached
+[2026-04-06 13:00:02] OK — 29 items cached
+[2026-04-06 13:05:02] OK — 29 items cached
+[2026-04-06 13:10:01] OK — 29 items cached
+[2026-04-06 13:15:03] OK — 29 items cached
+[2026-04-06 13:20:01] OK — 29 items cached
+[2026-04-06 13:25:02] OK — 29 items cached
+[2026-04-06 13:30:01] OK — 29 items cached
+[2026-04-06 13:35:01] OK — 29 items cached
+[2026-04-06 13:40:02] OK — 29 items cached
+[2026-04-06 13:45:01] OK — 29 items cached
+[2026-04-06 13:50:02] OK — 29 items cached
+[2026-04-06 13:55:02] OK — 29 items cached
+[2026-04-06 14:00:01] OK — 29 items cached
+[2026-04-06 14:05:02] OK — 29 items cached
+[2026-04-06 14:10:02] OK — 29 items cached
+[2026-04-06 14:15:02] OK — 29 items cached
+[2026-04-06 14:20:02] OK — 29 items cached
+[2026-04-06 14:25:01] OK — 29 items cached
+[2026-04-06 14:30:02] OK — 29 items cached
+[2026-04-06 14:35:02] OK — 29 items cached
+[2026-04-06 14:40:01] OK — 29 items cached
+[2026-04-06 14:45:01] OK — 29 items cached
+[2026-04-06 14:50:02] OK — 29 items cached
+[2026-04-06 14:55:02] OK — 29 items cached
+[2026-04-06 15:00:07] OK — 29 items cached
+[2026-04-06 15:05:01] OK — 29 items cached
+[2026-04-06 15:10:02] OK — 29 items cached
+[2026-04-06 15:15:02] OK — 29 items cached
+[2026-04-06 15:20:02] OK — 29 items cached
+[2026-04-06 15:25:02] OK — 29 items cached
+[2026-04-06 15:30:02] OK — 29 items cached
+[2026-04-06 15:35:01] OK — 29 items cached
+[2026-04-06 15:40:02] OK — 29 items cached
+[2026-04-06 15:45:02] OK — 29 items cached
+[2026-04-06 15:50:01] OK — 29 items cached
+[2026-04-06 15:55:02] OK — 29 items cached
+[2026-04-06 16:00:02] OK — 29 items cached
+[2026-04-06 16:05:02] OK — 29 items cached
+[2026-04-06 16:10:01] OK — 29 items cached
+[2026-04-06 16:15:02] OK — 29 items cached
+[2026-04-06 16:20:02] OK — 29 items cached
+[2026-04-06 16:25:02] OK — 29 items cached
+[2026-04-06 16:30:01] OK — 29 items cached
+[2026-04-06 16:35:01] OK — 29 items cached
+[2026-04-06 16:40:02] OK — 29 items cached
+[2026-04-06 16:45:02] OK — 29 items cached
+[2026-04-06 16:50:01] OK — 29 items cached
+[2026-04-06 16:55:02] OK — 29 items cached
+[2026-04-06 17:00:02] OK — 29 items cached
+[2026-04-06 17:05:02] OK — 29 items cached
+[2026-04-06 17:10:01] OK — 29 items cached
+[2026-04-06 17:15:02] OK — 29 items cached
+[2026-04-06 17:20:02] OK — 29 items cached
+[2026-04-06 17:25:02] OK — 29 items cached
+[2026-04-06 17:30:01] OK — 29 items cached
+[2026-04-06 17:35:02] OK — 29 items cached
+[2026-04-06 17:40:02] OK — 29 items cached
+[2026-04-06 17:45:01] OK — 29 items cached
+[2026-04-06 17:50:02] OK — 29 items cached
+[2026-04-06 17:55:02] OK — 29 items cached
+[2026-04-06 18:00:01] OK — 29 items cached
+[2026-04-06 18:05:02] OK — 29 items cached
+[2026-04-06 18:10:02] OK — 29 items cached
+[2026-04-06 18:15:02] OK — 29 items cached
+[2026-04-06 18:20:01] OK — 29 items cached
+[2026-04-06 18:25:02] OK — 29 items cached
+[2026-04-06 18:30:02] OK — 29 items cached
+[2026-04-06 18:35:01] OK — 29 items cached
+[2026-04-06 18:40:02] OK — 29 items cached
+[2026-04-06 18:45:02] OK — 29 items cached
+[2026-04-06 18:50:01] OK — 29 items cached
+[2026-04-06 18:55:02] OK — 29 items cached
+[2026-04-06 19:00:02] OK — 29 items cached
+[2026-04-06 19:05:02] OK — 29 items cached
+[2026-04-06 19:10:01] OK — 29 items cached
+[2026-04-06 19:15:02] OK — 29 items cached
+[2026-04-06 19:20:02] OK — 29 items cached
+[2026-04-06 19:25:02] OK — 29 items cached
+[2026-04-06 19:30:02] OK — 29 items cached
+[2026-04-06 19:35:01] OK — 29 items cached
+[2026-04-06 19:40:02] OK — 29 items cached
+[2026-04-06 19:45:02] OK — 29 items cached
+[2026-04-06 19:50:01] OK — 29 items cached
+[2026-04-06 19:55:02] OK — 29 items cached
+[2026-04-06 20:00:02] OK — 29 items cached
+[2026-04-06 20:05:01] OK — 29 items cached
+[2026-04-06 20:10:02] OK — 29 items cached
+[2026-04-06 20:15:02] OK — 29 items cached
+[2026-04-06 20:20:02] OK — 29 items cached
+[2026-04-06 20:25:02] OK — 29 items cached
+[2026-04-06 20:30:01] OK — 29 items cached
+[2026-04-06 20:35:02] OK — 29 items cached
+[2026-04-06 20:40:02] OK — 29 items cached
+[2026-04-06 20:45:01] OK — 29 items cached
+[2026-04-06 20:50:02] OK — 29 items cached
+[2026-04-06 20:55:01] OK — 29 items cached
+[2026-04-06 21:00:02] OK — 29 items cached
+[2026-04-06 21:05:02] OK — 29 items cached
+[2026-04-06 21:10:01] OK — 29 items cached
+[2026-04-06 21:15:02] OK — 29 items cached
+[2026-04-06 21:20:02] OK — 29 items cached
+[2026-04-06 21:25:02] OK — 29 items cached
+[2026-04-06 21:30:01] OK — 29 items cached
+[2026-04-06 21:35:02] OK — 29 items cached
+[2026-04-06 21:40:02] OK — 29 items cached
+[2026-04-06 21:45:01] OK — 29 items cached
+[2026-04-06 21:50:02] OK — 29 items cached
+[2026-04-06 21:55:02] OK — 29 items cached
+[2026-04-06 22:00:02] OK — 29 items cached
+[2026-04-06 22:05:01] OK — 29 items cached
+[2026-04-06 22:10:02] OK — 29 items cached
+[2026-04-06 22:15:02] OK — 29 items cached
+[2026-04-06 22:20:02] OK — 29 items cached
+[2026-04-06 22:25:01] OK — 29 items cached
+[2026-04-06 22:30:02] OK — 29 items cached
+[2026-04-06 22:35:01] OK — 29 items cached
+[2026-04-06 22:40:02] OK — 29 items cached
+[2026-04-06 22:45:02] OK — 29 items cached
+[2026-04-06 22:50:02] OK — 29 items cached
+[2026-04-06 22:55:01] OK — 29 items cached
+[2026-04-06 23:00:02] OK — 29 items cached
+[2026-04-06 23:05:01] OK — 29 items cached
+[2026-04-06 23:10:02] OK — 29 items cached
+[2026-04-06 23:15:02] OK — 29 items cached
+[2026-04-06 23:20:02] OK — 29 items cached
+[2026-04-06 23:25:02] OK — 29 items cached
+[2026-04-06 23:30:01] OK — 29 items cached
+[2026-04-06 23:35:02] OK — 29 items cached
+[2026-04-06 23:40:02] OK — 29 items cached
+[2026-04-06 23:45:01] OK — 29 items cached
+[2026-04-06 23:50:02] OK — 29 items cached
+[2026-04-06 23:55:02] OK — 29 items cached
+[2026-04-07 00:00:01] OK — 30 items cached
+[2026-04-07 00:05:02] OK — 30 items cached
+[2026-04-07 00:10:01] OK — 30 items cached
+[2026-04-07 00:15:03] OK — 30 items cached
+[2026-04-07 00:20:01] OK — 30 items cached
+[2026-04-07 00:25:02] OK — 30 items cached
+[2026-04-07 00:30:02] OK — 30 items cached
+[2026-04-07 00:35:02] OK — 30 items cached
+[2026-04-07 00:40:01] OK — 30 items cached
+[2026-04-07 00:45:02] OK — 30 items cached
+[2026-04-07 00:50:01] OK — 30 items cached
+[2026-04-07 00:55:01] OK — 30 items cached
+[2026-04-07 01:00:02] OK — 30 items cached
+[2026-04-07 01:05:01] OK — 30 items cached
+[2026-04-07 01:10:02] OK — 30 items cached
+[2026-04-07 01:15:02] OK — 30 items cached
+[2026-04-07 01:20:02] OK — 30 items cached
+[2026-04-07 01:25:01] OK — 30 items cached
+[2026-04-07 01:30:02] OK — 30 items cached
+[2026-04-07 01:35:02] OK — 30 items cached
+[2026-04-07 01:40:01] OK — 30 items cached
+[2026-04-07 01:45:02] OK — 30 items cached
+[2026-04-07 01:50:02] OK — 30 items cached
+[2026-04-07 01:55:02] OK — 30 items cached
+[2026-04-07 02:00:01] OK — 30 items cached
+[2026-04-07 02:05:02] OK — 30 items cached
+[2026-04-07 02:10:02] OK — 30 items cached
+[2026-04-07 02:15:02] OK — 30 items cached
+[2026-04-07 02:20:01] OK — 30 items cached
+[2026-04-07 02:25:02] OK — 30 items cached
+[2026-04-07 02:30:02] OK — 30 items cached
+[2026-04-07 02:35:01] OK — 30 items cached
+[2026-04-07 02:40:02] OK — 30 items cached
+[2026-04-07 02:45:01] OK — 30 items cached
+[2026-04-07 02:50:02] OK — 30 items cached
+[2026-04-07 02:55:02] OK — 30 items cached
+[2026-04-07 03:00:02] OK — 30 items cached
+[2026-04-07 03:05:02] OK — 30 items cached
+[2026-04-07 03:10:01] OK — 30 items cached
+[2026-04-07 03:15:02] OK — 30 items cached
+[2026-04-07 03:20:02] OK — 30 items cached
+[2026-04-07 03:25:02] OK — 30 items cached
+[2026-04-07 03:30:01] OK — 30 items cached
+[2026-04-07 03:35:02] OK — 30 items cached
+[2026-04-07 03:40:02] OK — 30 items cached
+[2026-04-07 03:45:01] OK — 30 items cached
+[2026-04-07 03:50:02] OK — 30 items cached
+[2026-04-07 03:55:02] OK — 30 items cached
+[2026-04-07 04:00:01] OK — 30 items cached
+[2026-04-07 04:05:01] OK — 30 items cached
+[2026-04-07 04:10:02] OK — 30 items cached
+[2026-04-07 04:15:02] OK — 30 items cached
+[2026-04-07 04:20:02] OK — 30 items cached
+[2026-04-07 04:25:01] OK — 30 items cached
+[2026-04-07 04:30:02] OK — 30 items cached
+[2026-04-07 04:35:02] OK — 30 items cached
+[2026-04-07 04:40:01] OK — 30 items cached
+[2026-04-07 04:45:02] OK — 30 items cached
+[2026-04-07 04:50:02] OK — 30 items cached
+[2026-04-07 04:55:01] OK — 31 items cached
+[2026-04-07 05:00:02] OK — 31 items cached
+[2026-04-07 05:05:02] OK — 31 items cached
+[2026-04-07 05:10:02] OK — 31 items cached
+[2026-04-07 05:15:02] OK — 31 items cached
+[2026-04-07 05:20:02] OK — 31 items cached
+[2026-04-07 05:25:01] OK — 31 items cached
+[2026-04-07 05:30:02] OK — 31 items cached
+[2026-04-07 05:35:02] OK — 31 items cached
+[2026-04-07 05:40:01] OK — 31 items cached
+[2026-04-07 05:45:02] OK — 31 items cached
+[2026-04-07 05:50:01] OK — 31 items cached
+[2026-04-07 05:55:02] OK — 31 items cached
+[2026-04-07 06:00:02] OK — 31 items cached
+[2026-04-07 06:05:02] OK — 31 items cached
+[2026-04-07 06:10:01] OK — 31 items cached
+[2026-04-07 06:15:02] OK — 31 items cached
+[2026-04-07 06:20:02] OK — 31 items cached
+[2026-04-07 06:25:02] OK — 31 items cached
+[2026-04-07 06:30:01] OK — 31 items cached
+[2026-04-07 06:35:02] OK — 31 items cached
+[2026-04-07 06:40:02] OK — 31 items cached
+[2026-04-07 06:45:01] OK — 31 items cached
+[2026-04-07 06:50:02] OK — 31 items cached
+[2026-04-07 06:55:02] OK — 31 items cached
+[2026-04-07 07:00:02] OK — 31 items cached
+[2026-04-07 07:05:01] OK — 31 items cached
+[2026-04-07 07:10:02] OK — 31 items cached
+[2026-04-07 07:15:02] OK — 31 items cached
+[2026-04-07 07:20:02] OK — 31 items cached
+[2026-04-07 07:25:01] OK — 31 items cached
+[2026-04-07 07:30:02] OK — 31 items cached
+[2026-04-07 07:35:01] OK — 31 items cached
+[2026-04-07 07:40:02] OK — 31 items cached
+[2026-04-07 07:45:02] OK — 31 items cached
+[2026-04-07 07:50:01] OK — 31 items cached
+[2026-04-07 07:55:02] OK — 31 items cached
+[2026-04-07 08:00:02] OK — 31 items cached
+[2026-04-07 08:05:01] OK — 31 items cached
+[2026-04-07 08:10:02] OK — 31 items cached
+[2026-04-07 08:15:02] OK — 31 items cached
+[2026-04-07 08:20:02] OK — 31 items cached
+[2026-04-07 08:25:02] OK — 31 items cached
+[2026-04-07 08:30:01] OK — 31 items cached
+[2026-04-07 08:35:02] OK — 31 items cached
+[2026-04-07 08:40:02] OK — 31 items cached
+[2026-04-07 08:45:01] OK — 31 items cached
+[2026-04-07 08:50:02] OK — 31 items cached
+[2026-04-07 08:55:01] OK — 31 items cached
+[2026-04-07 09:00:02] OK — 31 items cached
+[2026-04-07 09:05:02] OK — 31 items cached
+[2026-04-07 09:10:01] OK — 31 items cached
+[2026-04-07 09:15:03] OK — 31 items cached
+[2026-04-07 09:20:01] OK — 31 items cached
+[2026-04-07 09:25:02] OK — 31 items cached
+[2026-04-07 09:30:02] OK — 31 items cached
+[2026-04-07 09:35:01] OK — 31 items cached
+[2026-04-07 09:40:02] OK — 31 items cached
+[2026-04-07 09:45:01] OK — 31 items cached
+[2026-04-07 09:50:02] OK — 31 items cached
+[2026-04-07 09:55:02] OK — 31 items cached
+[2026-04-07 10:00:01] OK — 31 items cached
+[2026-04-07 10:05:02] OK — 31 items cached
+[2026-04-07 10:10:02] OK — 31 items cached
+[2026-04-07 10:15:02] OK — 31 items cached
+[2026-04-07 10:20:01] OK — 31 items cached
+[2026-04-07 10:25:02] OK — 31 items cached
+[2026-04-07 10:30:02] OK — 31 items cached
+[2026-04-07 10:35:01] OK — 31 items cached
+[2026-04-07 10:40:02] OK — 31 items cached
+[2026-04-07 10:45:02] OK — 31 items cached
+[2026-04-07 10:50:01] OK — 31 items cached
+[2026-04-07 10:55:02] OK — 31 items cached
+[2026-04-07 11:00:02] OK — 31 items cached
+[2026-04-07 11:05:01] OK — 31 items cached
+[2026-04-07 11:10:02] OK — 31 items cached
+[2026-04-07 11:15:02] OK — 31 items cached
+[2026-04-07 11:20:02] OK — 31 items cached
+[2026-04-07 11:25:01] OK — 31 items cached
+[2026-04-07 11:30:02] OK — 31 items cached
+[2026-04-07 11:35:02] OK — 31 items cached
+[2026-04-07 11:40:01] OK — 31 items cached
+[2026-04-07 11:45:02] OK — 31 items cached
+[2026-04-07 11:50:01] OK — 31 items cached
+[2026-04-07 11:55:02] OK — 31 items cached
+[2026-04-07 12:00:02] OK — 31 items cached
+[2026-04-07 12:05:02] OK — 31 items cached
+[2026-04-07 12:10:01] OK — 30 items cached
diff --git a/data/dispensa.db b/data/dispensa.db
index 753240d..d8a1b6c 100644
Binary files a/data/dispensa.db and b/data/dispensa.db differ
diff --git a/data/smart_shopping_cache.json b/data/smart_shopping_cache.json
index ec86792..7e6564a 100644
--- a/data/smart_shopping_cache.json
+++ b/data/smart_shopping_cache.json
@@ -1 +1 @@
-{"success":true,"items":[{"product_id":151,"name":"Arance Tarocco","brand":"","category":"frutta","unit":"pz","current_qty":0,"default_qty":0,"package_unit":"","pct_left":0,"use_count":9,"buy_count":2,"daily_rate":0.51,"uses_per_month":12.5,"days_since_last_use":4,"days_left":0,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"critical","reasons":["Esaurito","Uso frequente (9x)"],"score":135,"on_bring":true,"locations":""},{"product_id":70,"name":"Latte Parzialmente Scremato Uht","brand":"Latteria","category":"bevande","unit":"conf","current_qty":0,"default_qty":500,"package_unit":"ml","pct_left":0,"use_count":13,"buy_count":3,"daily_rate":6.3,"uses_per_month":14.5,"days_since_last_use":11,"days_left":0,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"critical","reasons":["Esaurito","Uso frequente (13x)"],"score":135,"on_bring":true,"locations":""},{"product_id":47,"name":"Lenticchie","brand":"Primia","category":"en:plant-based-foods-and-beverages","unit":"g","current_qty":0,"default_qty":400,"package_unit":"","pct_left":0,"use_count":5,"buy_count":3,"daily_rate":33.53,"uses_per_month":5.6,"days_since_last_use":2,"days_left":0,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"critical","reasons":["Esaurito","Uso frequente (5x)"],"score":130,"on_bring":true,"locations":""},{"product_id":115,"name":"Aglio rosso","brand":"Duoccio","category":"condimenti","unit":"pz","current_qty":0,"default_qty":0,"package_unit":"","pct_left":0,"use_count":2,"buy_count":2,"daily_rate":0.13,"uses_per_month":2.5,"days_since_last_use":17,"days_left":0,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"critical","reasons":["Esaurito"],"score":100,"on_bring":true,"locations":""},{"product_id":152,"name":"Muesli Frutta Secca","brand":"Crownfield","category":"altro","unit":"g","current_qty":0,"default_qty":750,"package_unit":"","pct_left":0,"use_count":4,"buy_count":2,"daily_rate":30.88,"uses_per_month":6.3,"days_since_last_use":11,"days_left":0,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"critical","reasons":["Esaurito"],"score":100,"on_bring":false,"locations":""},{"product_id":3,"name":"Cracker integrali","brand":"Barilla,Mulino Bianco","category":"en:snacks","unit":"conf","current_qty":2,"default_qty":25,"package_unit":"g","pct_left":12,"use_count":6,"buy_count":1,"daily_rate":0.52,"uses_per_month":6.7,"days_since_last_use":8,"days_left":4,"expiry_date":"2026-04-28","days_to_expiry":22,"is_opened":true,"urgency":"high","reasons":["Quasi finito (12%)"],"score":90,"on_bring":true,"locations":"dispensa"},{"product_id":154,"name":"Mela Rossa","brand":"","category":"frutta","unit":"pz","current_qty":6,"default_qty":1,"package_unit":"","pct_left":27,"use_count":6,"buy_count":1,"daily_rate":0.85,"uses_per_month":9.5,"days_since_last_use":2,"days_left":7,"expiry_date":"2026-04-15","days_to_expiry":9,"is_opened":false,"urgency":"medium","reasons":["Finisce tra ~7gg"],"score":60,"on_bring":false,"locations":"frigo"},{"product_id":129,"name":"Latte di Montagna","brand":"Mila","category":"en:dairies","unit":"conf","current_qty":1,"default_qty":1000,"package_unit":"ml","pct_left":67,"use_count":11,"buy_count":4,"daily_rate":0.23,"uses_per_month":15.3,"days_since_last_use":0,"days_left":4,"expiry_date":"2026-04-09","days_to_expiry":3,"is_opened":true,"urgency":"medium","reasons":["Scade tra 3gg"],"score":55,"on_bring":true,"locations":"frigo"},{"product_id":132,"name":"Noci sgusciate","brand":"Fruttbella","category":"conserve","unit":"g","current_qty":60,"default_qty":200,"package_unit":"","pct_left":30,"use_count":4,"buy_count":1,"daily_rate":6.47,"uses_per_month":5.5,"days_since_last_use":2,"days_left":9,"expiry_date":"2026-04-29","days_to_expiry":23,"is_opened":true,"urgency":"medium","reasons":["Finisce tra ~9gg"],"score":50,"on_bring":true,"locations":"dispensa"},{"product_id":10,"name":"Passata di pomodoro","brand":"Primia","category":"en:condiments","unit":"g","current_qty":292,"default_qty":700,"package_unit":"","pct_left":44,"use_count":7,"buy_count":3,"daily_rate":38.23,"uses_per_month":7.8,"days_since_last_use":2,"days_left":8,"expiry_date":"2026-04-09","days_to_expiry":3,"is_opened":true,"urgency":"medium","reasons":["Scade tra 3gg"],"score":50,"on_bring":false,"locations":"frigo"},{"product_id":69,"name":"Cipolla Dorata","brand":"","category":"verdura","unit":"pz","current_qty":3,"default_qty":0,"package_unit":"","pct_left":33,"use_count":13,"buy_count":1,"daily_rate":0.34,"uses_per_month":14.5,"days_since_last_use":2,"days_left":9,"expiry_date":"2026-04-13","days_to_expiry":7,"is_opened":false,"urgency":"low","reasons":["Previsto esaurimento tra ~9gg"],"score":40,"on_bring":false,"locations":"frigo"},{"product_id":34,"name":"Tortiglioni","brand":"Barilla, Barilla Pasta","category":"en:plant-based-foods-and-beverages","unit":"g","current_qty":270,"default_qty":500,"package_unit":"","pct_left":73,"use_count":1,"buy_count":1,"daily_rate":3.72,"uses_per_month":1.1,"days_since_last_use":4,"days_left":73,"expiry_date":"2026-04-09","days_to_expiry":3,"is_opened":true,"urgency":"medium","reasons":["Scade tra 3gg"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":136,"name":"Biscotti Pastefrolle","brand":"Balocco","category":"snack","unit":"g","current_qty":350,"default_qty":700,"package_unit":"","pct_left":67,"use_count":4,"buy_count":2,"daily_rate":32.36,"uses_per_month":5.5,"days_since_last_use":2,"days_left":11,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"low","reasons":["Previsto esaurimento tra ~11gg"],"score":25,"on_bring":false,"locations":"dispensa"}],"cached_at":"2026-04-06T08:50:01+00:00","cached_ts":1775465401}
\ No newline at end of file
+{"success":true,"items":[{"product_id":70,"name":"Latte Parzialmente Scremato Uht","brand":"Latteria","category":"bevande","unit":"conf","current_qty":0,"default_qty":500,"package_unit":"ml","pct_left":0,"use_count":13,"buy_count":3,"daily_rate":6.04,"uses_per_month":13.9,"days_since_last_use":12,"days_left":0,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"critical","reasons":["Esaurito","Uso frequente (13x)"],"score":135,"on_bring":false,"locations":""},{"product_id":10,"name":"Passata di pomodoro","brand":"Primia","category":"en:condiments","unit":"g","current_qty":0,"default_qty":700,"package_unit":"","pct_left":0,"use_count":8,"buy_count":3,"daily_rate":47.09,"uses_per_month":8.6,"days_since_last_use":1,"days_left":0,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"critical","reasons":["Esaurito","Uso frequente (8x)"],"score":135,"on_bring":false,"locations":""},{"product_id":115,"name":"Aglio rosso","brand":"Duoccio","category":"condimenti","unit":"pz","current_qty":0,"default_qty":0,"package_unit":"","pct_left":0,"use_count":2,"buy_count":2,"daily_rate":0.12,"uses_per_month":2.4,"days_since_last_use":18,"days_left":0,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"critical","reasons":["Esaurito"],"score":100,"on_bring":false,"locations":""},{"product_id":152,"name":"Muesli Frutta Secca","brand":"Crownfield","category":"altro","unit":"g","current_qty":0,"default_qty":750,"package_unit":"","pct_left":0,"use_count":4,"buy_count":2,"daily_rate":29.14,"uses_per_month":5.9,"days_since_last_use":12,"days_left":0,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"critical","reasons":["Esaurito"],"score":100,"on_bring":false,"locations":""},{"product_id":69,"name":"Cipolla Dorata","brand":"","category":"verdura","unit":"pz","current_qty":1.8,"default_qty":0,"package_unit":"","pct_left":19,"use_count":15,"buy_count":1,"daily_rate":0.37,"uses_per_month":16.1,"days_since_last_use":1,"days_left":5,"expiry_date":"2026-04-13","days_to_expiry":5,"is_opened":false,"urgency":"high","reasons":["Finisce tra ~5gg"],"score":90,"on_bring":false,"locations":"frigo"},{"product_id":3,"name":"Cracker integrali","brand":"Barilla,Mulino Bianco","category":"en:snacks","unit":"conf","current_qty":2,"default_qty":25,"package_unit":"g","pct_left":12,"use_count":6,"buy_count":1,"daily_rate":0.5,"uses_per_month":6.4,"days_since_last_use":9,"days_left":4,"expiry_date":"2026-04-28","days_to_expiry":20,"is_opened":true,"urgency":"high","reasons":["Quasi finito (12%)"],"score":90,"on_bring":false,"locations":"dispensa"},{"product_id":47,"name":"Lenticchie","brand":"Primia","category":"en:plant-based-foods-and-beverages","unit":"conf","current_qty":2,"default_qty":400,"package_unit":"g","pct_left":1,"use_count":5,"buy_count":4,"daily_rate":32.16,"uses_per_month":5.4,"days_since_last_use":3,"days_left":0,"expiry_date":"2029-12-13","days_to_expiry":1345,"is_opened":false,"urgency":"high","reasons":["Quasi finito (1%)"],"score":90,"on_bring":false,"locations":"dispensa"},{"product_id":161,"name":"Yogurt agrumi","brand":"Vipiteno","category":"latticini","unit":"conf","current_qty":0.3,"default_qty":500,"package_unit":"g","pct_left":26,"use_count":2,"buy_count":1,"daily_rate":0.65,"uses_per_month":1,"days_since_last_use":1,"days_left":0,"expiry_date":"2026-04-09","days_to_expiry":1,"is_opened":true,"urgency":"medium","reasons":["Finisce tra ~0gg","Scade tra 1gg"],"score":90,"on_bring":false,"locations":"frigo"},{"product_id":154,"name":"Mela Rossa","brand":"","category":"frutta","unit":"pz","current_qty":6,"default_qty":1,"package_unit":"","pct_left":27,"use_count":6,"buy_count":1,"daily_rate":0.8,"uses_per_month":9,"days_since_last_use":3,"days_left":8,"expiry_date":"2026-04-15","days_to_expiry":7,"is_opened":false,"urgency":"medium","reasons":["Finisce tra ~8gg"],"score":60,"on_bring":false,"locations":"frigo"},{"product_id":144,"name":"Pancetta Dolce","brand":"Negroni","category":"en:meats-and-their-products","unit":"conf","current_qty":1,"default_qty":80,"package_unit":"g","pct_left":50,"use_count":1,"buy_count":1,"daily_rate":0.04,"uses_per_month":1.3,"days_since_last_use":8,"days_left":23,"expiry_date":"2026-09-11","days_to_expiry":156,"is_opened":false,"urgency":"high","reasons":["Solo 1 confezione rimasta"],"score":60,"on_bring":true,"locations":"frigo"},{"product_id":29,"name":"Sale marino iodato","brand":"Primia","category":"","unit":"pz","current_qty":1,"default_qty":1,"package_unit":"","pct_left":100,"use_count":0,"buy_count":1,"daily_rate":0,"uses_per_month":0,"days_since_last_use":28,"days_left":365,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"high","reasons":["Solo 1 pezzo rimasto"],"score":60,"on_bring":false,"locations":"dispensa"},{"product_id":56,"name":"Tonno all'Olio di Oliva","brand":"Rio Mare","category":"conserve","unit":"pz","current_qty":1,"default_qty":1,"package_unit":"","pct_left":50,"use_count":1,"buy_count":1,"daily_rate":0.04,"uses_per_month":1.1,"days_since_last_use":26,"days_left":28,"expiry_date":"2029-12-31","days_to_expiry":1363,"is_opened":false,"urgency":"high","reasons":["Solo 1 pezzo rimasto"],"score":60,"on_bring":false,"locations":"dispensa"},{"product_id":163,"name":"Yogurt vaniglia","brand":"Vipiteno","category":"latticini","unit":"conf","current_qty":1,"default_qty":500,"package_unit":"g","pct_left":100,"use_count":0,"buy_count":1,"daily_rate":0,"uses_per_month":0,"days_since_last_use":1,"days_left":365,"expiry_date":"2026-05-19","days_to_expiry":41,"is_opened":false,"urgency":"high","reasons":["Solo 1 confezione rimasta"],"score":60,"on_bring":false,"locations":"frigo"},{"product_id":106,"name":"Zuppalatte","brand":"Colussi","category":"en:plant-based-foods-and-beverages","unit":"conf","current_qty":1,"default_qty":250,"package_unit":"g","pct_left":33,"use_count":1,"buy_count":1,"daily_rate":0.08,"uses_per_month":1.2,"days_since_last_use":25,"days_left":12,"expiry_date":"2027-06-01","days_to_expiry":419,"is_opened":false,"urgency":"high","reasons":["Solo 1 confezione rimasta"],"score":60,"on_bring":false,"locations":"dispensa"},{"product_id":166,"name":"mix energia","brand":"","category":"","unit":"pz","current_qty":1,"default_qty":1,"package_unit":"","pct_left":100,"use_count":0,"buy_count":1,"daily_rate":0,"uses_per_month":0,"days_since_last_use":1,"days_left":365,"expiry_date":"2026-11-29","days_to_expiry":235,"is_opened":false,"urgency":"high","reasons":["Solo 1 pezzo rimasto"],"score":60,"on_bring":false,"locations":"dispensa"},{"product_id":132,"name":"Noci sgusciate","brand":"Fruttbella","category":"conserve","unit":"g","current_qty":60,"default_qty":200,"package_unit":"","pct_left":30,"use_count":4,"buy_count":1,"daily_rate":6.15,"uses_per_month":5.3,"days_since_last_use":3,"days_left":10,"expiry_date":"2026-04-29","days_to_expiry":21,"is_opened":true,"urgency":"medium","reasons":["Finisce tra ~10gg"],"score":50,"on_bring":true,"locations":"dispensa"},{"product_id":130,"name":"Uova Sfoglia Gialla","brand":"Naturelle","category":"latticini","unit":"pz","current_qty":4,"default_qty":1,"package_unit":"","pct_left":67,"use_count":5,"buy_count":2,"daily_rate":0.35,"uses_per_month":6.6,"days_since_last_use":1,"days_left":11,"expiry_date":"2026-04-09","days_to_expiry":1,"is_opened":false,"urgency":"medium","reasons":["Scade tra 1gg"],"score":50,"on_bring":false,"locations":"frigo"},{"product_id":87,"name":"Aroma per dolci e creme Rum","brand":"Dr. Oetker, Cameo, Paneangeli","category":"en:food-additives","unit":"pz","current_qty":2,"default_qty":5,"package_unit":"","pct_left":40,"use_count":3,"buy_count":1,"daily_rate":0.11,"uses_per_month":3.4,"days_since_last_use":3,"days_left":18,"expiry_date":"2028-09-28","days_to_expiry":904,"is_opened":false,"urgency":"medium","reasons":["Solo 2 pezzi rimasti"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":86,"name":"Arome Orange Pour Gateaux","brand":"Dr. Oetker, Cameo, Paneangeli","category":"","unit":"pz","current_qty":2,"default_qty":1,"package_unit":"","pct_left":67,"use_count":1,"buy_count":1,"daily_rate":0.04,"uses_per_month":1.1,"days_since_last_use":7,"days_left":54,"expiry_date":"2027-02-01","days_to_expiry":299,"is_opened":false,"urgency":"medium","reasons":["Solo 2 pezzi rimasti"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":170,"name":"Avocado Hass","brand":"Alce nero","category":"en:plant-based-foods-and-beverages","unit":"pz","current_qty":2,"default_qty":2,"package_unit":"","pct_left":100,"use_count":0,"buy_count":1,"daily_rate":0,"uses_per_month":0,"days_since_last_use":1,"days_left":365,"expiry_date":"2026-10-03","days_to_expiry":178,"is_opened":false,"urgency":"medium","reasons":["Solo 2 pezzi rimasti"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":8,"name":"Condoro Sugo Al Pomodoro Sauce Le Conserve Della Nonna","brand":"Fini, Le conserve della nonna","category":"en:condiments","unit":"conf","current_qty":2,"default_qty":350,"package_unit":"g","pct_left":1,"use_count":0,"buy_count":2,"daily_rate":0,"uses_per_month":0,"days_since_last_use":28,"days_left":365,"expiry_date":"2028-07-31","days_to_expiry":845,"is_opened":false,"urgency":"medium","reasons":["Solo 2 confezioni rimaste"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":1,"name":"Grissini","brand":"GrissinBon","category":"en:plant-based-foods-and-beverages","unit":"conf","current_qty":2,"default_qty":500,"package_unit":"","pct_left":100,"use_count":0,"buy_count":1,"daily_rate":0,"uses_per_month":0,"days_since_last_use":28,"days_left":365,"expiry_date":"2026-07-09","days_to_expiry":92,"is_opened":false,"urgency":"medium","reasons":["Solo 2 confezioni rimaste"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":129,"name":"Latte di Montagna","brand":"Mila","category":"en:dairies","unit":"conf","current_qty":4.8,"default_qty":1000,"package_unit":"ml","pct_left":192,"use_count":14,"buy_count":6,"daily_rate":0.45,"uses_per_month":18.4,"days_since_last_use":0,"days_left":11,"expiry_date":"2026-04-11","days_to_expiry":3,"is_opened":true,"urgency":"low","reasons":["Previsto esaurimento tra ~11gg"],"score":40,"on_bring":false,"locations":"frigo"},{"product_id":92,"name":"Lievito istantaneo","brand":"Paneangeli","category":"altro","unit":"g","current_qty":5,"default_qty":15,"package_unit":"","pct_left":33,"use_count":1,"buy_count":1,"daily_rate":0.37,"uses_per_month":1.1,"days_since_last_use":27,"days_left":13,"expiry_date":"2026-04-10","days_to_expiry":2,"is_opened":true,"urgency":"medium","reasons":["Scade tra 2gg"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":55,"name":"Mais Dolce","brand":"Despar","category":"conserve","unit":"conf","current_qty":2,"default_qty":160,"package_unit":"g","pct_left":0,"use_count":0,"buy_count":1,"daily_rate":0,"uses_per_month":0,"days_since_last_use":28,"days_left":365,"expiry_date":"2028-12-31","days_to_expiry":998,"is_opened":false,"urgency":"medium","reasons":["Solo 2 confezioni rimaste"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":39,"name":"Panna da cucina","brand":"Chef, Parmalat","category":"altro","unit":"conf","current_qty":2,"default_qty":125,"package_unit":"g","pct_left":67,"use_count":2,"buy_count":1,"daily_rate":0.01,"uses_per_month":2.1,"days_since_last_use":8,"days_left":138,"expiry_date":"2026-04-29","days_to_expiry":21,"is_opened":true,"urgency":"medium","reasons":["Solo 2 confezioni rimaste"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":2,"name":"Pera Italiana Succo e polpa frutta","brand":"Conserve Italia, Valfrutta","category":"bevande","unit":"conf","current_qty":2,"default_qty":200,"package_unit":"ml","pct_left":50,"use_count":3,"buy_count":1,"daily_rate":0.11,"uses_per_month":3.2,"days_since_last_use":2,"days_left":19,"expiry_date":"2027-02-01","days_to_expiry":299,"is_opened":false,"urgency":"medium","reasons":["Solo 2 confezioni rimaste"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":127,"name":"Polpa di pomodoro finissima","brand":"Primia","category":"en:plant-based-foods-and-beverages","unit":"conf","current_qty":2,"default_qty":500,"package_unit":"g","pct_left":1,"use_count":0,"buy_count":2,"daily_rate":0,"uses_per_month":0,"days_since_last_use":23,"days_left":365,"expiry_date":"2028-03-14","days_to_expiry":706,"is_opened":false,"urgency":"medium","reasons":["Solo 2 confezioni rimaste"],"score":40,"on_bring":false,"locations":"dispensa"},{"product_id":114,"name":"Vino bianco Trebbiano igt Rubicone","brand":"Vesoletto","category":"bevande","unit":"conf","current_qty":2,"default_qty":250,"package_unit":"ml","pct_left":100,"use_count":1,"buy_count":1,"daily_rate":0,"uses_per_month":1.2,"days_since_last_use":4,"days_left":365,"expiry_date":"2027-04-03","days_to_expiry":360,"is_opened":false,"urgency":"medium","reasons":["Solo 2 confezioni rimaste"],"score":40,"on_bring":false,"locations":"frigo"},{"product_id":136,"name":"Biscotti Pastefrolle","brand":"Balocco","category":"snack","unit":"g","current_qty":350,"default_qty":700,"package_unit":"","pct_left":67,"use_count":4,"buy_count":2,"daily_rate":30.74,"uses_per_month":5.3,"days_since_last_use":3,"days_left":11,"expiry_date":null,"days_to_expiry":999,"is_opened":false,"urgency":"low","reasons":["Previsto esaurimento tra ~11gg"],"score":25,"on_bring":false,"locations":"dispensa"}],"cached_at":"2026-04-07T12:10:01+00:00","cached_ts":1775563801}
\ No newline at end of file