Skip to content

refactor(categorisation): flatten the category taxonomy to 27 direction-filtered categories - #37

Merged
suiramdev merged 2 commits into
devfrom
refactor/flat-transaction-taxonomy
Sep 19, 2026
Merged

suiramdev merged 2 commits into
devfrom
refactor/flat-transaction-taxonomy

Conversation

@suiramdev

Copy link
Copy Markdown
Owner

Summary

Replaces the 75-category, 16-group hierarchy with 27 categories under three sections — income, investments, spending. The picker now filters by the transaction's direction: 20 categories on an outgoing transaction, 10 on an incoming one. Start with packages/api/src/lib/taxonomy.ts; every other change follows from it. No migration: the decoder reads an old slug on the way out.

Motivation

  • A topic taxonomy made every signal guess which of sixteen shelves a merchant sat on, and made the reader learn them.
  • Offering salary on a card payment was the largest single source of wrong picks; direction is already known from the sign.
  • Insurance was a category competing with the thing insured, so the same policy landed in two places depending on the signal that found it.
  • An internal transfer was both a category and a boolean column, and the two could disagree.

Drawbacks

  • 27 categories lose precision the old set carried: flights, accommodation and taxi all become transport-travel. A reader who used the fine grain loses it.
  • Stored slugs are never rewritten, so LEGACY_CATEGORY_SLUGS (70 entries) has to stay until a data-* release is rebuilt from the new taxonomy.
  • Custom-category budget lines fold into their section's fallback in the two re-keyed charts.
  • The jev provider's group question drops from 17 options to 4 while its spending leaf question grows to 16; the 0.5 joint-probability floor was not retuned for that shape.

Prior art

  • 20260831120000_category_hierarchy did the opposite move with a SQL remap; this one relies on the read-path decoder instead, so no downgrade is one-way.
  • Colour and icon already existed per group; moving them per category reuses categoryColor and categoryIcon unchanged.
  • TAXONOMY_VERSION already existed for exactly this case and goes to 2, invalidating the classification cache.

Notes

  • Visual change: category pickers, the spending donut, budget-vs-actual, and the settings category list.
  • No migration added. lookupUserOverride no longer casts merchant_override.category; proved against a seeded stale row, and mutation-checked against the raw cast.
  • Tests: deterministic.test.ts gains readsAsRefund direction cases and a rule that no word naming a movement names a category; mcc-categories.test.ts, transaction-category.test.ts and jev.test.ts re-grounded on the new slugs.
  • Docs updated: guides/categories, concepts, integrations/procedures, contributing/categorisation, contributing/data-model, plus docs/engineering/{api,categorisation,data-pipeline}.md and packages/api/data/README.md. The data-model migration table was two rows stale and is corrected.
  • Rebased onto feat: pluggable transaction classifier, and a sync that reports its progress #36; the classifier and sync-progress work landed mid-change, and the keyword tables, MCC ranges and jev fixtures were reconciled with it rather than reverted.
  • Verified in a browser on a clean stack: 20/10 picker rows, Sankey and donut keyed by category, override decode.

…on-filtered categories

Replace the 75-category, 16-group hierarchy with 27 user-visible categories
under three sections: income, investments and spending. The picker filters by
the transaction's direction, so an outgoing transaction offers 20 categories
and an incoming one offers 10.

Colour and icon move from the group to the category, because three sections
would otherwise paint all sixteen spending rows one colour. The spending
breakdown and the budget-vs-actual chart re-key from group to category for the
same reason.

Insurance loses its category and folds into the thing being insured. An
internal transfer is no longer a category: Transaction.isInternalTransfer
already carries it, so a paired leg keeps its pipeline-resolved category.

No migration. LEGACY_CATEGORY_SLUGS decodes the stored and prebuilt-artifact
labels on read, and every read path resolves an unknown slug instead of
casting it.
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: c19f4870-24d4-4222-880c-f1597e11b4e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…question

The direction-filtered picker created an invariant no producer enforced: a
category the user cannot select on a row must never be written to it. The MCC
branch of the deterministic layer skipped the refund guard its sibling branch
ran, the dictionary answered the same category for a refund as for a purchase,
and the classifier was offered the whole taxonomy whatever the sign.

Jev is now asked one direction-filtered question instead of a group question
and a leaf question per group. The cascade existed for 75 categories under 16
groups; 27 do not need it, and greedy two-stage search cannot recover from a
wrong group. The acceptance bar moves from a product of two picked
probabilities to the answer's own confidence, which is the statistic TypeSafe
documents for thresholding and which does not move with the size of the option
set. Each option carries a rubric rather than a bare label.
@suiramdev
suiramdev merged commit c325c69 into dev Sep 19, 2026
3 checks passed
@suiramdev
suiramdev deleted the refactor/flat-transaction-taxonomy branch September 19, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant