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
| Capability | Description |
|---|---|
| Receipt scan | POST /finance/receipts/scan — multipart image → OpenAI vision (gpt-4o-mini) → structured preview |
| Confirm | POST /finance/receipts/{scanId}/confirm — creates transaction, links receipt, records product observations (single DB transaction for receipt + lines + observations) |
| Receipt image | GET /finance/receipts/{id}/image — authenticated download |
| Product price browser | Moved — 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_KEYon 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
- Finance → Scan receipt (receipt icon).
- Pick an image (JPEG/PNG/WebP, max 10 MB).
- Review store, total, category, account, and editable line items (name, qty, unit price, line total; add/remove lines).
- Confirm → expense transaction is created and the receipt image is stored.
- 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:
- Create a bucket (e.g.
oter-receipts-{env}). - Grant the Cloud Run runtime service account
roles/storage.objectAdminon that bucket. - Extend
ReceiptImageServiceto write/readgs://keys (same API shape as local paths). - Keep serving images through
GET /finance/receipts/{id}/imageso 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_keyunique per user). - Each confirmed line creates a
product_price_observationsrow withsource = RECEIPTandreceipt_line_item_idset, 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.
| Method | Path | Notes |
|---|---|---|
POST | /finance/receipts/scan | multipart file |
POST | /finance/receipts/{scanId}/confirm | ConfirmReceiptScanRequest 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.
| Layer | Location |
|---|---|
| Migration | V11__receipts_and_product_prices.sql (plus product-side V16/V19/V31/V33 — listed in the Products doc) |
| Entities | server/.../database/entities/ReceiptEntities.kt (shared with Products) |
| AI | server/.../service/ReceiptScanAiService.kt |
| Storage | server/.../service/ReceiptImageService.kt |
| Receipt orchestration | server/.../service/ReceiptService.kt (calls ProductPriceService.recordObservationInTx per line item with source = RECEIPT) |
| Receipt routes | server/.../routing/FinanceRouting.kt (/finance/receipts/*) |
| KMP client | shared/.../services/finance/FinanceService.kt |
| UI | shared/.../oter/finance/ui/screens/ReceiptScanScreen.kt, ReceiptScanPreviewScreen.kt |
Related
- Products & price tracking — the canonical guide for the post-confirmation price-history surface
- Finance overview — core finance module
- gcp-server-cloud-run.md —
OPENAI_API_KEYvia Secret Manager