feat: pluggable transaction classifier, and a sync that reports its progress - #36
Conversation
… classifier Replace the local n-gram model, its trainer and the SIRENE/NAF dictionary enrichment with a model-agnostic TransactionClassifier, a first `jev` provider, and a classification cache keyed by merchant signature. Reads now take one path: a correction, else the stored pipeline verdict, else uncategorised.
Budget used to show an empty list while a first sync ran. A SyncRun row now records the phase and the counters of the sync a user has in flight, and is also the lock that refuses a second one. The budget area polls it, shows a progress panel, refreshes the lists every hundred transactions, and keeps both pages in a loading shape until the first rows arrive.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Verification against the dev stack (OrbStack, real Postgres, real provider refusal). Screenshots do not upload from the CLI, so each state is described with what was measured on it in the browser. Importing. Categorising. Transactions stream in. 120 rows inserted while the run was live: the totals went Empty states. With a live run and no history, Transactions renders 29 skeletons and Recurring 15, and neither shows its empty state. With 100 transactions present and a filter that matches none, both show the real empty state and zero skeletons — the loading state follows Locks. A second Counters. 100 candidate rows of which 40 match an MCC rule → Classifier. Through Motion. The panel enters with |
Summary
Two commits. The first replaces the semantic tail of
packages/api/src/categorisation— the n-gram model, its trainer and the SIRENE/NAF pass — with a model-agnosticTransactionClassifier, a firstjevprovider behindTRANSACTION_CLASSIFIER, and a cache keyed by merchant signature. The second gives the sync a voice: async_runrow records the phase and counters of the run a user has in flight, Budget polls it, and the lists refresh every hundred transactions. Readresolve.ts › classifyPendingandsync-progress.ts › claimSyncRunfirst.Motivation
No transactionsfor minutes: the page looked finished and empty while the work ran.Drawbacks
uncategorised, notother-income. Sign-split totals do not move; only the label does.Prior art
packages/email/src/registry.ts: one factory, aResult, a startup refusal.user-override.ts: a cache or progress failure never fails a sync.Notes
20260919120000_merchant_classificationand20260920090000_sync_run; the first backfillsresolutionStage.TRANSACTION_CLASSIFIER,TYPESAFE_API_KEY,TYPESAFE_MODEL.jevwithout a key refuses at startup.train:model,data/model-weights.json, the SIRENE client and cache, and the SIRENE steps ofgenerate-data.bun test packages/api/src apps/web/src: 441 pass, 1 fail —merchant-key.test.ts › drops deferred and immediate debit markersneedsdata/place-tokens.jsonand fails the same way ondev.contributing/{categorisation,data-model},guides/bank-connections,integrations/procedures,self-hosting/configuration,docs/engineering/{api,web-app,categorisation,data-pipeline}.md.revieweragent ran over each half and returnedapproved, empty findings. It caught an IBAN in the classifier payload and a superseded reporter that could release a live run's lock; both are fixed.