From 65317659217f40a833f06e9ff49c9441d03979a6 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Thu, 9 Apr 2026 12:56:10 +0000 Subject: [PATCH] =?UTF-8?q?Priorit=C3=A0=20verdura/frutta=20aperta=20in=20?= =?UTF-8?q?ricette=20+=20stime=20corrette?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stime opened (PHP+JS): - Avocado -> 2d; fragole/banane/pesca/mango -> 2d - Mela/pera/kiwi/ananas/melone/uva -> 3d - Zucchina/melanzana/pomodoro/peperone -> 3d (era 7d) - Broccolo/cavolfiore/sedano/finocchio -> 3d (era 7d) - Cipolla/cipollotto/scalogno/porro -> 4d (era 7d) - Carota -> 5d (era 7d); Aglio -> 10d - Insalata/rucola/spinaci -> 2d (era 4d) Recipe generator (index.php): - SQL include i.opened_at - getItemPriority: rileva opened_at su QUALSIASI unitΓ  (non solo conf) - Elementi [APERTO] con scadenza <=5gg promossi in 'fortemente consigliati' - Label 'πŸ“¦ PRODOTTI APERTI' aggiornata - [APERTO] mostrato nel testo ingredienti del prompt AI - Stesso fix nel contesto chat (opened_at + [APERTO] label) DB migration: ricalcolate scadenze aperti con nuove stime --- api/database.php | 22 ++++++++++++++++------ api/index.php | 35 ++++++++++++++++++++++++++--------- assets/js/app.js | 22 ++++++++++++++++------ 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/api/database.php b/api/database.php index 016a9d5..96377bb 100644 --- a/api/database.php +++ b/api/database.php @@ -273,15 +273,25 @@ function estimateOpenedExpiryDaysPHP(string $name, string $category, string $loc if (preg_match('/\b(pollo|tacchino|maiale|manzo|vitello|agnello)\b/', $n)) return 2; if (preg_match('/salmone|tonno\s+fresco|pesce(?!\s+in)/', $n)) return 2; if (preg_match('/\b(passata|pelati|polpa|sugo|salsa\s+di\s+pomodoro)\b/', $n)) return 5; - if (preg_match('/insalata|rucola|spinaci|lattuga/', $n)) return 4; - if (preg_match('/\b(succo|spremuta)\b/', $n)) return 5; - if (preg_match('/\blimone\b/', $n)) return 14; + if (preg_match('/insalata|rucola|spinaci|lattuga|crescione|germogli/', $n)) return 2; + if (preg_match('/\b(succo|spremuta)\b/', $n)) return 3; if (preg_match('/\b(birra|beer)\b/', $n)) return 3; if (preg_match('/\bvino\b/', $n)) return 5; if (preg_match('/tonno\s+in\s+scatola|tonno\s+rio|sgombro\s+in/', $n)) return 4; - if (preg_match('/\b(banana|banane|mela|pera|pesca|albicocca|ciliegia|uva|fragola|lampone|kiwi)\b/', $n)) return 10; - if (preg_match('/\b(arancia|mandarino|pompelmo|clementina)\b/', $n)) return 14; - if (preg_match('/\b(carota|zucchina|peperone|melanzana|broccolo|cavolfiore|sedano|finocchio)\b/', $n)) return 7; + // Fruit opened/cut in fridge β€” much shorter than sealed + if (preg_match('/\bavocado\b/', $n)) return 2; + if (preg_match('/\b(banana|banane|fragola|lampone|pesca|albicocca|ciliegia|mango|papaya)\b/', $n)) return 2; + if (preg_match('/\b(mela|pera|nettarina|prugna|kiwi|ananas|uva|melone|anguria)\b/', $n)) return 3; + if (preg_match('/\b(arancia|mandarino|pompelmo|clementina|limone)\b/', $n)) return 3; // cut citrus + // Vegetables opened/cut in fridge + if (preg_match('/\b(zucchina|zucchine|melanzana|pomodor)\b/', $n)) return 3; + if (preg_match('/\b(peperone|peperoni)\b/', $n)) return 3; + if (preg_match('/\b(broccolo|broccoli|cavolfiore|cavolo)\b/', $n)) return 3; + if (preg_match('/\bsedano\b|\bfinocchio\b/', $n)) return 3; + if (preg_match('/\b(cipolla|cipolle|cipollotto|scalogno|porro)\b/', $n)) return 4; + if (preg_match('/\b(carota|carote)\b/', $n)) return 5; + if (preg_match('/\b(patata|patate|tubero)\b/', $n)) return 3; // cooked/cut potato + if (preg_match('/\baglio\b/', $n)) return 10; // ── G: Fridge condiments β€” medium shelf-life ───────────────────────── if (preg_match('/maionese|mayo|mayon/', $n)) return 90; diff --git a/api/index.php b/api/index.php index 6407e9d..3fb9a8d 100644 --- a/api/index.php +++ b/api/index.php @@ -1364,7 +1364,7 @@ function geminiChat(PDO $db): void { // Fetch inventory context $stmt = $db->query(" - SELECT p.name, p.brand, p.category, i.quantity, p.unit, p.default_quantity, p.package_unit, i.location, i.expiry_date, + SELECT p.name, p.brand, p.category, i.quantity, p.unit, p.default_quantity, p.package_unit, i.location, i.expiry_date, i.opened_at, CASE WHEN i.expiry_date IS NOT NULL THEN julianday(i.expiry_date) - julianday('now') ELSE 999 END AS days_left FROM inventory i JOIN products p ON p.id = i.product_id @@ -1381,6 +1381,9 @@ function geminiChat(PDO $db): void { if ($item['unit'] === 'conf' && !empty($item['package_unit']) && $item['default_quantity'] > 0) { $line .= " (da {$item['default_quantity']} {$item['package_unit']} ciascuna)"; } + $isOpen = !empty($item['opened_at']) || + (floatval($item['quantity']) > 0 && floatval($item['quantity']) < 1 && $item['unit'] === 'conf'); + if ($isOpen) $line .= ' [APERTO]'; if ($item['expiry_date']) { $daysLeft = intval($item['days_left']); if ($daysLeft < 0) { @@ -1527,7 +1530,7 @@ function generateRecipe(PDO $db): void { // Fetch all inventory items with expiry info $stmt = $db->query(" - SELECT p.id AS product_id, p.name, p.brand, p.category, i.quantity, p.unit, p.default_quantity, p.package_unit, i.location, i.expiry_date, + SELECT p.id AS product_id, p.name, p.brand, p.category, i.quantity, p.unit, p.default_quantity, p.package_unit, i.location, i.expiry_date, i.opened_at, CASE WHEN i.expiry_date IS NOT NULL THEN julianday(i.expiry_date) - julianday('now') ELSE 999 END AS days_left FROM inventory i JOIN products p ON p.id = i.product_id @@ -1542,10 +1545,14 @@ function generateRecipe(PDO $db): void { } // Helper to compute priority group for an item: - // 1=scaduto, 2=scadenza imminente ≀3gg, 3=scadenza ravvicinata ≀7gg, 4=scadenza lontana, 5=confezione aperta, 6=chiuso + // 1=scaduto, 2=scadenza imminente ≀3gg, 3=scadenza ravvicinata ≀7gg, + // 4=scadenza lontana, 5=aperto (opened_at set o conf parziale), 6=chiuso $getItemPriority = function($item) { $daysLeft = floatval($item['days_left']); - $isOpen = (floatval($item['quantity']) > 0 && floatval($item['quantity']) < 1 && $item['unit'] === 'conf'); + // "Aperto" = opened_at Γ¨ impostato (frutta/verdura/qualsiasi cosa usata parzialmente) + // OPPURE confezione parzialmente usata (qty < 1 conf) + $isOpen = !empty($item['opened_at']) || + (floatval($item['quantity']) > 0 && floatval($item['quantity']) < 1 && $item['unit'] === 'conf'); if (!empty($item['expiry_date']) && $daysLeft < 0) return 1; if (!empty($item['expiry_date']) && $daysLeft <= 3) return 2; if (!empty($item['expiry_date']) && $daysLeft <= 7) return 3; @@ -1568,7 +1575,7 @@ function generateRecipe(PDO $db): void { 2 => 'πŸ”΄ SCADENZA IMMINENTE (entro 3 giorni - USA PER PRIMI)', 3 => '🟠 SCADENZA RAVVICINATA (entro 7 giorni)', 4 => '🟑 ALTRI PRODOTTI CON SCADENZA', - 5 => 'πŸ“¦ CONFEZIONI APERTE (da consumare prima dei chiusi)', + 5 => 'πŸ“¦ PRODOTTI APERTI (giΓ  aperti/tagliati β€” da consumare prima delle confezioni chiuse)', 6 => '🟒 ALTRI PRODOTTI', ]; $priorityGroups = []; @@ -1591,8 +1598,10 @@ function generateRecipe(PDO $db): void { $line .= " [FRIGO]"; } $qty = floatval($item['quantity']); - if ($qty > 0 && $qty < 1 && $item['unit'] === 'conf') { - $line .= " [APERTO]"; + $isOpen = !empty($item['opened_at']) || + ($qty > 0 && $qty < 1 && $item['unit'] === 'conf'); + if ($isOpen) { + $line .= ' [APERTO]'; } $line .= " (in {$item['location']})"; @@ -1619,11 +1628,19 @@ function generateRecipe(PDO $db): void { foreach ($items as $item) { $g = $getItemPriority($item); $daysLeft = floatval($item['days_left']); - $label = $item['name'] . ($item['brand'] ? " ({$item['brand']})" : '') . " β€” scade: {$item['expiry_date']}"; + $isOpen = !empty($item['opened_at']) || + (floatval($item['quantity']) > 0 && floatval($item['quantity']) < 1 && $item['unit'] === 'conf'); + $expiryNote = !empty($item['expiry_date']) ? " β€” scade: {$item['expiry_date']}" : ''; + $openNote = $isOpen ? ' [APERTO]' : ''; + $label = $item['name'] . ($item['brand'] ? " ({$item['brand']})" : '') . $openNote . $expiryNote; if ($g === 1 || ($g === 2 && $daysLeft <= 1)) { $mandatoryItems[] = $label; } elseif ($g === 2) { $recommendedItems[] = $label; + } elseif ($isOpen && $daysLeft <= 5 && $daysLeft >= 0) { + // Opened items expiring within 5 days but not already in mandatory/recommended + // β†’ bump to "strongly recommended" so the AI knows to use them + $recommendedItems[] = $label; } } @@ -1632,7 +1649,7 @@ function generateRecipe(PDO $db): void { $mustUseText .= "\n\n⚠️⚠️⚠️ INGREDIENTI OBBLIGATORI (SCADUTI O IN SCADENZA OGGI/DOMANI) ⚠️⚠️⚠️\nLa ricetta DEVE usare ALMENO uno (meglio se tutti) di questi ingredienti come ingrediente PRINCIPALE. Non sono opzionali!\n" . implode("\n", array_map(fn($n) => "β†’ $n", $mandatoryItems)); } if (!empty($recommendedItems)) { - $mustUseText .= "\n\nπŸ”Ά INGREDIENTI FORTEMENTE CONSIGLIATI (scadono tra 2-3 giorni)\nCerca di includerli se la ricetta lo permette, ma non Γ¨ obbligatorio se non si abbinano bene:\n" . implode("\n", array_map(fn($n) => "Β· $n", $recommendedItems)); + $mustUseText .= "\n\nπŸ”Ά INGREDIENTI FORTEMENTE CONSIGLIATI (aperti e/o in scadenza a breve)\nSono giΓ  aperti e/o scadono presto β€” includi piΓΉ di questi possibile nella ricetta:\n" . implode("\n", array_map(fn($n) => "Β· $n", $recommendedItems)); } $mealLabels = [ diff --git a/assets/js/app.js b/assets/js/app.js index cd69c90..d58f1bc 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -447,15 +447,25 @@ function estimateOpenedExpiryDays(product, location) { if (/\b(pollo|tacchino|maiale|manzo|vitello|agnello)\b/.test(name)) return 2; if (/salmone|tonno\s+fresco|pesce(?!\s+in)/.test(name)) return 2; if (/\b(passata|pelati|polpa|sugo|salsa\s+di\s+pomodoro)\b/.test(name)) return 5; - if (/insalata|rucola|spinaci|lattuga/.test(name)) return 4; - if (/\b(succo|spremuta)\b/.test(name)) return 5; - if (/\blimone\b/.test(name)) return 14; + if (/insalata|rucola|spinaci|lattuga|crescione|germogli/.test(name)) return 2; + if (/\b(succo|spremuta)\b/.test(name)) return 3; if (/\b(birra|beer)\b/.test(name)) return 3; if (/\bvino\b/.test(name)) return 5; if (/tonno\s+in\s+scatola|tonno\s+rio|sgombro\s+in/.test(name)) return 4; - if (/\b(banana|banane|mela|pera|pesca|albicocca|ciliegia|uva|fragola|lampone|kiwi)\b/.test(name)) return 10; - if (/\b(arancia|mandarino|pompelmo|clementina)\b/.test(name)) return 14; - if (/\b(carota|zucchina|peperone|melanzana|broccolo|cavolfiore|sedano|finocchio)\b/.test(name)) return 7; + // Fruit opened/cut in fridge + if (/\bavocado\b/.test(name)) return 2; + if (/\b(banana|banane|fragola|lampone|pesca|albicocca|ciliegia|mango|papaya)\b/.test(name)) return 2; + if (/\b(mela|pera|nettarina|prugna|kiwi|ananas|uva|melone|anguria)\b/.test(name)) return 3; + if (/\b(arancia|mandarino|pompelmo|clementina|limone)\b/.test(name)) return 3; + // Vegetables opened/cut in fridge + if (/\b(zucchina|zucchine|melanzana|pomodor)\b/.test(name)) return 3; + if (/\b(peperone|peperoni)\b/.test(name)) return 3; + if (/\b(broccolo|broccoli|cavolfiore|cavolo)\b/.test(name)) return 3; + if (/\bsedano\b|\bfinocchio\b/.test(name)) return 3; + if (/\b(cipolla|cipolle|cipollotto|scalogno|porro)\b/.test(name)) return 4; + if (/\b(carota|carote)\b/.test(name)) return 5; + if (/\b(patata|patate|tubero)\b/.test(name)) return 3; + if (/\baglio\b/.test(name)) return 10; // ── G: Fridge condiments ───────────────────────────────────────────── if (/maionese|mayo|mayon/.test(name)) return 90;