feat: improved receipt recognition, batch product add, and scan UX

- Rewrite receipt OCR prompt: completes truncated names, preserves fat%
  and flavour attributes, extracts weight/volume from line, infers
  typical package sizes for solid goods with quantity_confidence field
- Add quantity_confidence to RecognizedItem, EnrichedItem, and
  ProductJobResultItem; propagate through item enricher and worker
- Replace per-item create loop with single POST /user-products/batch call
  from RecognitionConfirmScreen
- Rebuild RecognitionConfirmScreen: amber qty border for low
  quantity_confidence, tappable product name → catalog picker,
  sort items by confidence, full L10n (no hardcoded strings)
- Add timestamps (HH:mm / d MMM HH:mm) to recent scan chips
- Show close-app hint on ProductJobWatchScreen (queued + processing)
- Refresh recentProductJobsProvider on watch screen init so new job
  appears without a manual pull-to-refresh
- App-level WidgetsBindingObserver refreshes product and dish job lists
  on resume, fixing stale lists after background/foreground transitions
- Add 9 new L10n keys across all 12 locales

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-03-26 23:09:57 +02:00
parent b2bdcbae6f
commit 5c5ed25e5b
38 changed files with 1221 additions and 115 deletions

View File

@@ -103,13 +103,14 @@ type imagesRequest struct {
// EnrichedItem is a recognized food item enriched with ingredient_mappings data.
type EnrichedItem struct {
Name string `json:"name"`
Quantity float64 `json:"quantity"`
Unit string `json:"unit"`
Category string `json:"category"`
Confidence float64 `json:"confidence"`
MappingID *string `json:"mapping_id"`
StorageDays int `json:"storage_days"`
Name string `json:"name"`
Quantity float64 `json:"quantity"`
Unit string `json:"unit"`
Category string `json:"category"`
Confidence float64 `json:"confidence"`
QuantityConfidence float64 `json:"quantity_confidence"`
MappingID *string `json:"mapping_id"`
StorageDays int `json:"storage_days"`
}
// ReceiptResponse is the response for POST /ai/recognize-receipt.

View File

@@ -26,12 +26,13 @@ func (enricher *itemEnricher) enrich(enrichContext context.Context, items []ai.R
result := make([]EnrichedItem, 0, len(items))
for _, item := range items {
enriched := EnrichedItem{
Name: item.Name,
Quantity: item.Quantity,
Unit: item.Unit,
Category: item.Category,
Confidence: item.Confidence,
StorageDays: 7, // sensible default
Name: item.Name,
Quantity: item.Quantity,
Unit: item.Unit,
Category: item.Category,
Confidence: item.Confidence,
QuantityConfidence: item.QuantityConfidence,
StorageDays: 7, // sensible default
}
catalogProduct, matchError := enricher.productRepo.FuzzyMatch(enrichContext, item.Name)

View File

@@ -20,13 +20,14 @@ type ProductImagePayload struct {
// ProductJobResultItem is an enriched product item stored in the result JSONB.
type ProductJobResultItem struct {
Name string `json:"name"`
Quantity float64 `json:"quantity"`
Unit string `json:"unit"`
Category string `json:"category"`
Confidence float64 `json:"confidence"`
MappingID *string `json:"mapping_id,omitempty"`
StorageDays int `json:"storage_days"`
Name string `json:"name"`
Quantity float64 `json:"quantity"`
Unit string `json:"unit"`
Category string `json:"category"`
Confidence float64 `json:"confidence"`
QuantityConfidence float64 `json:"quantity_confidence"`
MappingID *string `json:"mapping_id,omitempty"`
StorageDays int `json:"storage_days"`
}
// ProductJobResult is the JSONB payload stored in product_recognition_jobs.result.

View File

@@ -127,13 +127,14 @@ func (pool *ProductWorkerPool) processJob(workerContext context.Context, jobID s
resultItems := make([]ProductJobResultItem, len(enriched))
for index, item := range enriched {
resultItems[index] = ProductJobResultItem{
Name: item.Name,
Quantity: item.Quantity,
Unit: item.Unit,
Category: item.Category,
Confidence: item.Confidence,
MappingID: item.MappingID,
StorageDays: item.StorageDays,
Name: item.Name,
Quantity: item.Quantity,
Unit: item.Unit,
Category: item.Category,
Confidence: item.Confidence,
QuantityConfidence: item.QuantityConfidence,
MappingID: item.MappingID,
StorageDays: item.StorageDays,
}
}