Skip to Content
FeaturesFinanceBills (receipt scan)

Bills reader (receipt scan)

Scan receipt images into finance transactions with line-item extraction, receipt image attachment, and product price history for downstream store comparison. The price browser, manual entry, web search, and watched products now live in the dedicated Products & price tracking module — this guide stays focused on the receipt ingestion path.

Overview

CapabilityDescription
Receipt scanPOST /finance/receipts/scan — multipart image → OpenAI vision (gpt-4o-mini) → structured preview
ConfirmPOST /finance/receipts/{scanId}/confirm — creates transaction, links receipt, records product observations (single DB transaction for receipt + lines + observations)
Receipt imageGET /finance/receipts/{id}/image — authenticated download
Product price browserMoved — see Products & price tracking for GET /products, /products/{id}, /products/{id}/stores, manual entry, web search, and watched products

Clients: Desktop and Android (Compose) for the scan-and-confirm flow. Product price browsing is available on all four targets (Android, iOS, Desktop, Web).

Requirements

  • OPENAI_API_KEY on the server (same as task import AI and chat). If unset, scan returns 503 / “AI is unavailable”.
  • FINANCE feature flag enabled for the platform.

User flow

  1. Finance → Scan receipt (receipt icon).
  2. Pick an image (JPEG/PNG/WebP, max 10 MB).
  3. Review store, total, category, account, and editable line items (name, qty, unit price, line total; add/remove lines).
  4. Confirm → expense transaction is created and the receipt image is stored.
  5. Confirmed lines flow into the per-user product catalog as price observations with source = RECEIPT. Browsing, search, and store comparison happen in Products & price tracking.

Transaction detail shows the receipt image when receiptId is present on the transaction.

Recipe editors load the product catalog with optional price stats (includeStats=true) and show an estimated ingredient cost when products are linked — see the Products doc for the catalog endpoint.

Storage

Development

Receipt files are stored under local-storage/receipts/{userId}/ on the server filesystem (ReceiptImageService). The database stores a storage_key path, not a public URL.

Production (Cloud Run)

Cloud Run instances are ephemeral. Use Google Cloud Storage for durable receipt blobs:

  1. Create a bucket (e.g. oter-receipts-{env}).
  2. Grant the Cloud Run runtime service account roles/storage.objectAdmin on that bucket.
  3. Extend ReceiptImageService to write/read gs:// keys (same API shape as local paths).
  4. Keep serving images through GET /finance/receipts/{id}/image so clients never see raw bucket URLs.

Until GCS is wired, production may lose receipt files on revision recycle; plan GCS before relying on receipts in prod.

Product matching (write side)

  • Line items from AI include optional normalizedName.
  • Server normalizes names (lowercase, trim, strip SKU prefixes) via ProductNameNormalizer.
  • Store names are normalized on write via StoreNameNormalizer (trim, lowercase) so variants like “Carrefour” / “CARREFOUR” aggregate together.
  • Products are per user (products.normalized_key unique per user).
  • Each confirmed line creates a product_price_observations row with source = RECEIPT and receipt_line_item_id set, store-name normalized.

Read-side behavior (best store, trend, search, web search, watched products) is documented in the Products module guide.

Receipt API

Receipt routes still live under /finance/receipts/*. The product browsing/search/watch routes have moved to top-level /products/* — see the Products doc for that surface.

MethodPathNotes
POST/finance/receipts/scanmultipart file
POST/finance/receipts/{scanId}/confirmConfirmReceiptScanRequest JSON
GET/finance/receipts/{id}/image

Shared models: shared/.../oter/finance/model/ReceiptModels.kt.

Code map (receipt path only)

For the full product-prices code map (services, schedulers, screens, migrations), see the Products doc.

LayerLocation
MigrationV11__receipts_and_product_prices.sql (plus product-side V16/V19/V31/V33 — listed in the Products doc)
Entitiesserver/.../database/entities/ReceiptEntities.kt (shared with Products)
AIserver/.../service/ReceiptScanAiService.kt
Storageserver/.../service/ReceiptImageService.kt
Receipt orchestrationserver/.../service/ReceiptService.kt (calls ProductPriceService.recordObservationInTx per line item with source = RECEIPT)
Receipt routesserver/.../routing/FinanceRouting.kt (/finance/receipts/*)
KMP clientshared/.../services/finance/FinanceService.kt
UIshared/.../oter/finance/ui/screens/ReceiptScanScreen.kt, ReceiptScanPreviewScreen.kt