feat: AI price estimation for shopping list with per-item real-time display

- Add get_shopping_price / get_all_shopping_prices API endpoints
- AI (Gemini) estimates retail price per natural unit (pack, piece, bunch)
  instead of always per-kg — avoids absurd totals like €1609
- _calcEstimatedTotal: proper g/ml→package conversion using defQty + regex
  on unit_label; only 'kg'/'l' labels trigger weight/volume math
- Cache key bumped to v2 to invalidate old per-kg cached entries
- Suggested quantity cap lowered from 20 to 10 conf/pz
- Unit mismatch guard: if totalUsed >> buyCount*5 for unit=conf, use
  purchase frequency instead of raw consumption rate
- JS _buildPricePayload: use smartShoppingItems for qty/unit (not Bring! spec)
- JS _cachedPrices: persist in sessionStorage (survives navigation);
  validated by _qty/_unit metadata so stale totals auto-invalidate
- Price display redesigned: right-side column per row (price-col-main +
  price-col-unit) instead of small inline badge
- fetchAllPrices: buttons disabled immediately before guard check;
  running total uses only current shoppingItems (not Object.values cache)
- Background refresh: always silent (removed 90s interaction condition)
- visibilitychange: sets _bgCall=true for shopping before refreshCurrentPage
- .gitignore: add runtime data files (bring_migrate_ts, shopping_price_cache,
  anomaly_dismissed, opened_shelf_cache, shopping_name_cache)
- Remove bring_catalog.json and bring_migrate_ts.json from tracking
This commit is contained in:
dadaloop82
2026-05-07 17:31:23 +00:00
parent 4196130835
commit 5f510c0451
11 changed files with 1249 additions and 743 deletions
+22 -6
View File
@@ -103,11 +103,13 @@
"banner_expired_action_throw": "Habe ich weggeworfen",
"banner_expired_action_edit": "Datum korrigieren",
"banner_anomaly_action_edit": "Bestand korrigieren",
"banner_anomaly_action_dismiss": "Menge ist korrekt", "banner_no_expiry_title": "Ablaufdatum fehlt: {name}",
"banner_anomaly_action_dismiss": "Menge ist korrekt",
"banner_no_expiry_title": "Ablaufdatum fehlt: {name}",
"banner_no_expiry_detail": "Dieses Produkt hat kein Ablaufdatum. Möchten Sie eines hinzufügen oder bestätigen, dass es nicht verfällt?",
"banner_no_expiry_action_set": "Ablaufdatum setzen",
"banner_no_expiry_action_dismiss": "Läuft nicht ab ✓",
"banner_no_expiry_toast_dismissed": "Als 'läuft nicht ab' markiert", "banner_expiring_title": "Bald ablaufend",
"banner_no_expiry_toast_dismissed": "Als 'läuft nicht ab' markiert",
"banner_expiring_title": "Bald ablaufend",
"banner_expiring_today": "Läuft heute ab!",
"banner_expiring_tomorrow": "Läuft morgen ab",
"banner_expiring_days": "Läuft in {days} Tagen ab",
@@ -133,8 +135,7 @@
"banner_anomaly_phantom_title": "mehr Bestand als erwartet",
"banner_anomaly_phantom_detail": "Bestand zeigt {inv_qty} {unit}, aber laut Buchungen solltest du nur {expected_qty} {unit} haben. Hast du Bestand ohne Buchung hinzugefügt?",
"banner_anomaly_untracked_title": "Anfangsbestand nicht als Eingang gebucht",
"banner_anomaly_untracked_detail": "Du hast <strong>{inv_qty} {unit}</strong> im Bestand, aber die gebuchten Abgänge übersteigen die Eingänge — der Anfangsbestand wurde wahrscheinlich nie als \"Eingang\" erfasst. Bitte korrigiere die Menge oder trage die fehlenden Eingänge nach."
,
"banner_anomaly_untracked_detail": "Du hast <strong>{inv_qty} {unit}</strong> im Bestand, aber die gebuchten Abgänge übersteigen die Eingänge — der Anfangsbestand wurde wahrscheinlich nie als \"Eingang\" erfasst. Bitte korrigiere die Menge oder trage die fehlenden Eingänge nach.",
"banner_anomaly_ghost_title": "weniger Bestand als erwartet",
"banner_anomaly_ghost_detail": "Laut Buchungen solltest du {expected_qty} {unit} von {name} haben, aber der Bestand zeigt nur {inv_qty} {unit}. Hast du etwas ohne Buchung entnommen?",
"consumed": "Verbraucht: {n} ({pct}%)",
@@ -161,7 +162,8 @@
"label_quantity": "📦 Menge",
"label_added": "📅 Hinzugefügt",
"empty_text": "Keine Produkte hier.<br>Scanne ein Produkt, um es hinzuzufügen!",
"empty_db": "Keine Produkte in der Datenbank.<br>Scanne ein Produkt, um loszulegen!"
"empty_db": "Keine Produkte in der Datenbank.<br>Scanne ein Produkt, um loszulegen!",
"qty_trace": "< 1"
},
"scan": {
"title": "Produkt scannen",
@@ -344,7 +346,7 @@
"suggestions_title": "💡 KI-Vorschläge",
"suggestions_add": "✅ Ausgewählte zu Bring! hinzufügen",
"search_prices": "🔍 Alle Preise suchen",
"suggest_btn": "🤖 Einkaufsvorschläge",
"suggest_btn": "Einkaufsvorschläge",
"smart_title": "🧠 Intelligente Vorhersagen",
"smart_empty": "Keine Vorhersagen verfügbar.<br>Füge Produkte zur Vorratskammer hinzu, um intelligente Vorhersagen zu erhalten.",
"smart_filter_all": "Alle",
@@ -416,6 +418,10 @@
"savings_offers": "· 🏷️ Du sparst €{amount} mit Angeboten",
"searching_progress": "Suche {current}/{total}...",
"remove_error": "Fehler beim Entfernen",
"btn_fetch_prices": "Preise suchen",
"price_total_label": "💰 Geschätzter Gesamtpreis:",
"price_loading": "Preise werden gesucht…",
"price_not_found": "Preis n/v",
"suggest_loading": "Analyse läuft...",
"suggest_error": "Fehler bei der Vorschlagserstellung",
"priority_high": "Hoch",
@@ -508,6 +514,15 @@
"email_label": "📧 Bring! E-Mail",
"password_label": "🔒 Bring! Passwort"
},
"price": {
"title": "💰 Preisschätzung (KI)",
"hint": "Zeigt geschätzte Kosten pro Produkt in der Einkaufsliste mithilfe von KI an.",
"enabled_label": "Preisschätzung aktivieren",
"country_label": "🌍 Referenzland",
"currency_label": "💱 Währung",
"update_label": "🔄 Preise aktualisieren alle",
"update_suffix": "Monate"
},
"recipe": {
"title": "🍳 Rezept-Einstellungen",
"hint": "Konfiguriere die Standardoptionen für die Rezeptgenerierung.",
@@ -906,6 +921,7 @@
},
"meal_plan": {
"reset_success": "Wochenplan zurückgesetzt",
"not_available": "nicht im Vorrat verfügbar",
"suggested_by": "vom Wochenplan vorgeschlagen"
},
"kiosk_session": {
+17 -2
View File
@@ -162,7 +162,8 @@
"label_quantity": "📦 Quantity",
"label_added": "📅 Added",
"empty_text": "No products here.<br>Scan a product to add it!",
"empty_db": "No products in the database.<br>Scan a product to get started!"
"empty_db": "No products in the database.<br>Scan a product to get started!",
"qty_trace": "< 1"
},
"scan": {
"title": "Scan Product",
@@ -345,7 +346,7 @@
"suggestions_title": "💡 AI Suggestions",
"suggestions_add": "✅ Add selected to Bring!",
"search_prices": "🔍 Search all prices",
"suggest_btn": "🤖 Suggest what to buy",
"suggest_btn": "Suggest what to buy",
"smart_title": "🧠 Smart Predictions",
"smart_empty": "No predictions available.<br>Add products to your pantry to receive smart predictions.",
"smart_filter_all": "All",
@@ -417,6 +418,10 @@
"savings_offers": "· 🏷️ You save €{amount} with offers",
"searching_progress": "Searching {current}/{total}...",
"remove_error": "Removal error",
"btn_fetch_prices": "Find prices",
"price_total_label": "💰 Estimated total:",
"price_loading": "Looking up prices…",
"price_not_found": "price n/a",
"suggest_loading": "Analyzing...",
"suggest_error": "Suggestion generation error",
"priority_high": "High",
@@ -509,6 +514,15 @@
"email_label": "📧 Bring! Email",
"password_label": "🔒 Bring! Password"
},
"price": {
"title": "💰 Price Estimation (AI)",
"hint": "Show estimated cost per product in the shopping list using AI.",
"enabled_label": "Enable price estimation",
"country_label": "🌍 Reference country",
"currency_label": "💱 Currency",
"update_label": "🔄 Refresh prices every",
"update_suffix": "months"
},
"recipe": {
"title": "🍳 Recipe Preferences",
"hint": "Configure the default options for recipe generation.",
@@ -907,6 +921,7 @@
},
"meal_plan": {
"reset_success": "Weekly plan reset",
"not_available": "not available in pantry",
"suggested_by": "suggested by weekly plan"
},
"kiosk_session": {
+18 -3
View File
@@ -162,7 +162,8 @@
"label_quantity": "📦 Quantità",
"label_added": "📅 Aggiunto",
"empty_text": "Nessun prodotto qui.<br>Scansiona un prodotto per aggiungerlo!",
"empty_db": "Nessun prodotto nel database.<br>Scansiona un prodotto per iniziare!"
"empty_db": "Nessun prodotto nel database.<br>Scansiona un prodotto per iniziare!",
"qty_trace": "< 1"
},
"scan": {
"title": "Scansiona Prodotto",
@@ -345,7 +346,7 @@
"suggestions_title": "💡 Suggerimenti AI",
"suggestions_add": "✅ Aggiungi selezionati a Bring!",
"search_prices": "🔍 Cerca tutti i prezzi",
"suggest_btn": "🤖 Suggerisci cosa comprare",
"suggest_btn": "Suggerisci cosa comprare",
"smart_title": "🧠 Previsioni intelligenti",
"smart_empty": "Nessuna previsione disponibile.<br>Aggiungi prodotti alla dispensa per ricevere previsioni intelligenti.",
"smart_filter_all": "Tutti",
@@ -417,6 +418,10 @@
"savings_offers": "· 🏷️ Risparmi €{amount} con le offerte",
"searching_progress": "Cerco {current}/{total}...",
"remove_error": "Errore nella rimozione",
"btn_fetch_prices": "Cerca i prezzi",
"price_total_label": "💰 Spesa stimata:",
"price_loading": "Ricerca prezzi…",
"price_not_found": "prezzo n/d",
"suggest_loading": "Analisi in corso...",
"suggest_error": "Errore nella generazione",
"priority_high": "Alta",
@@ -509,6 +514,15 @@
"email_label": "📧 Email Bring!",
"password_label": "🔒 Password Bring!"
},
"price": {
"title": "💰 Stima Prezzi (AI)",
"hint": "Mostra il costo stimato di ogni prodotto nella lista della spesa usando l'AI.",
"enabled_label": "Attiva stima prezzi",
"country_label": "🌍 Paese di riferimento",
"currency_label": "💱 Valuta",
"update_label": "🔄 Aggiorna prezzi ogni",
"update_suffix": "mesi"
},
"recipe": {
"title": "🍳 Preferenze Ricette",
"hint": "Configura le opzioni predefinite per la generazione delle ricette.",
@@ -907,7 +921,8 @@
},
"meal_plan": {
"reset_success": "Piano settimanale ripristinato",
"suggested_by": "suggerito dal piano settimanale"
"suggested_by": "suggerito dal piano settimanale",
"not_available": "non disponibile in dispensa"
},
"kiosk_session": {
"first_item": "Primo prodotto: {name}!",