Scoped per-receipt alias resolution (stop loading the full table) #17

Merged
julian merged 1 commit from feat/scoped-alias-pipeline into main 2026-06-23 22:03:52 +02:00
Owner

merchant_aliases (the line→group table) is shared reference data — one row per distinct product line per merchant, across all users. It can grow to millions of rows, yet the app loaded the entire table into every client on hydrate and mapAndBook did a linear .find() over it. This resolves aliases scoped to each scan instead, index-backed by the (merchant_id, normalized_string) primary key.

Changes

  • db: dbFetchAliasesFor(merchantId, normalizedStrings) — fetches only the rows for a receipt's merchant + line strings (≤ #lines, index-backed). dbFetchAllAliases(limit) for the admin allocate board (capped + logged — no silent truncation). fetchAllData no longer loads merchant_aliases; the in-store catalog starts empty.
  • store: addScannedReceipt / runPipeline / confirmGroup are now async — harmonize → fetch scoped aliases → mapAndBook against just those. loadAllAliases() populates the catalog for the admin board, which loads it on mount.
  • engine: unchanged — mapAndBook stays pure, it just receives a small scoped array.

Why it also fixes the "new scan doesn't pick up changes" report

Every scan now reads the current DB rows for its lines, so group/alias edits show up without an app restart — previously the in-memory catalog only refreshed on hydrate (launch/sign-in).

Cost

O(receipt size), not O(table size) — independent of total alias count.

Follow-up

The admin allocate "All" view still loads the (capped) catalog client-side; at scale it needs server-side pagination/search.

tsc clean · 52/52 booking tests · lint clean

🤖 Generated with Claude Code

`merchant_aliases` (the line→group table) is shared reference data — one row per distinct product line per merchant, across all users. It can grow to millions of rows, yet the app loaded the **entire table** into every client on hydrate and `mapAndBook` did a linear `.find()` over it. This resolves aliases **scoped to each scan** instead, index-backed by the `(merchant_id, normalized_string)` primary key. ### Changes - **db**: `dbFetchAliasesFor(merchantId, normalizedStrings)` — fetches only the rows for a receipt's merchant + line strings (≤ #lines, index-backed). `dbFetchAllAliases(limit)` for the admin allocate board (capped + logged — no silent truncation). `fetchAllData` no longer loads `merchant_aliases`; the in-store catalog starts empty. - **store**: `addScannedReceipt` / `runPipeline` / `confirmGroup` are now async — `harmonize` → fetch scoped aliases → `mapAndBook` against just those. `loadAllAliases()` populates the catalog for the admin board, which loads it on mount. - **engine**: unchanged — `mapAndBook` stays pure, it just receives a small scoped array. ### Why it also fixes the "new scan doesn't pick up changes" report Every scan now reads the current DB rows for its lines, so group/alias edits show up **without an app restart** — previously the in-memory catalog only refreshed on hydrate (launch/sign-in). ### Cost O(receipt size), not O(table size) — independent of total alias count. ### Follow-up The admin allocate "All" view still loads the (capped) catalog client-side; at scale it needs server-side pagination/search. tsc clean · 52/52 booking tests · lint clean 🤖 Generated with [Claude Code](https://claude.com/claude-code)
merchant_aliases (line→group) can grow to millions of rows; loading it
wholesale into every client on hydrate doesn't scale. Resolve aliases scoped
to each scan instead, index-backed by the (merchant_id, normalized_string) PK.

- db: add dbFetchAliasesFor(merchantId, lines) — fetches only a receipt's
  lines; dbFetchAllAliases(limit) for the admin board (capped + logged, no
  silent truncation). Stop loading merchant_aliases in fetchAllData (the
  in-store catalog now starts empty).
- store: addScannedReceipt / runPipeline / confirmGroup are async —
  harmonize -> fetch scoped aliases -> mapAndBook against just those. This also
  fixes staleness: every scan reads current DB rows, so group/alias edits show
  up without an app restart, with no millions-row transfer.
- store: loadAllAliases() populates the catalog for the admin allocate board;
  admin board loads it on mount.

Engine stays pure (receives a small scoped array). tsc clean, 52/52 booking tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
julian merged commit 2edfefedf0 into main 2026-06-23 22:03:52 +02:00
julian deleted branch feat/scoped-alias-pipeline 2026-06-23 22:03:59 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
julian/quickfin!17
No description provided.