Transaction Categorization With a Human in the Loop
A platform for a US advisory firm: QuickBooks transactions in, a category proposed inside the firm's own taxonomy or abstained, the rest routed to an analyst.

Problem
The firm's analysts moved every client's QuickBooks transactions into the firm's own categorization framework by hand, in spreadsheets. Errors flowed into client deliverables and reconciling them afterwards cost more than doing the work carefully. Chart-of-account labels are free text, so two clients in the same industry name the same expense five ways, and a single client's labels drift across years. The firm's taxonomy, not the model, is the source of truth: a system that suggests a category outside the framework is a system the firm cannot use. Split transactions are common and consequential, every decision has to be attributable to a person and a timestamp, and each of the firm's clients is a tenant whose data and learned model never leak across.
Solution
The taxonomy was encoded first as the constraint the model has to satisfy: the classifier proposes a category from inside the framework or abstains and routes the line to a reviewer, and never invents one. Each tenant gets its own classifier, fine-tuned on a finance-pretrained BERT model from the reviewer's confirmations and remaps, retrained nightly with the previous version retained for comparison. Two gates sit before anything is accepted without review: the model's confidence and explicit validation rules such as balance checks, split-sum checks and prohibited mappings. The platform is an ASP.NET Core REST API over a multi-tenant PostgreSQL schema that is audit-trailed end to end, a React review queue built with the senior analysts in the room, around the way they already worked in spreadsheets, and a Python machine-learning service beside the .NET backend. Long-lived services run on AWS ECS, per-transaction classification on Lambda. Delivered by 2 developers over 6 months.


