Complaint triage that sends the uncertain cases to a person.

Classifies US consumer financial complaints into ten products with distilled transformers, finds the relevant CFPB examination procedures, and decides whether each case is handled automatically or sent to a person.

The demo runs on a free server that may take a minute to wake.

Sector
Financial services
Method
NLP and retrieval
When
Mar – Oct 2026
Context
MSc Deep Learning for NLP coursework
Role
Solo: data cleaning, modelling, retrieval, routing and the web app

Key figures

  • 0.757 macro-F1 on 274,065 held-out complaints
  • 0.689 macro-F1 of the TF-IDF baseline
  • 92% of complaints handled automatically in offline tests
The Complaint Triage app classifying a credit-report complaint with 96.5% confidence and showing it would be handled automatically, next to the example complaints, two of which go to a person.

How it works

Built once, on one 8 GB GPU
  1. Clean : 13.9M complaints to 2.47M unique descriptions
  2. Split : Train before 2025, test from mid-2025
  3. Fine-tune : RoBERTa and ModernBERT
  4. Distil : BART-large-MNLI soft labels teach RoBERTa
  5. Stack : Logistic regression combines both models
  6. Learn routing : A bandit trained on 1.8M real outcomes
Runs for each complaint
  1. First model : Distilled RoBERTa; under 65% sure goes to a person
  2. Second model : ModernBERT reads the same complaint
  3. Combine : Stacked model; under 55% sure goes to a person
  4. Route : The bandit picks a model's answer, or a person
  5. Retrieve : The CFPB examination procedures that apply

Architecture and results

  • A model runs here
  • Deterministic code
  • An outside source
  • A stored artefact
  • A boundary the design refuses
  1. Two models, two gates, a bandit and a person

    Two transformers were fine-tuned on one 8 GB laptop GPU, and RoBERTa was distilled from a zero-shot teacher's soft labels. For each complaint, two confidence gates send unclear cases to a person; above them, a bandit trained on 1.8 million real outcomes picks a model's answer or a person, and BM25 finds the CFPB examination procedures that apply.

    Two rows. Built once: complaints, a time split, two transformers, distillation and a routing bandit. For each complaint: two gated models, the bandit, BM25 retrieval and the routed case. Open full size: Two models, two gates, a bandit and a person
  2. What each step added

    The TF-IDF baseline looked competitive on a 100,000-complaint sample, at 0.754, but scored 0.689 on the full data, so every later model is compared with 0.689. Distillation added two points over standard fine-tuning, and stacking both transformers added the last.

    Macro-F1 across ten products on held-out complaints (higher is better)

    What each step added
    Item Macro-F1
    TF-IDF baseline, full data 0.689
    RoBERTa, standard fine-tuning 0.730
    RoBERTa, distilled 0.750
    Stacked RoBERTa and ModernBERT 0.757
  3. Who handles each complaint

    In offline tests the router handled 92% of complaints automatically and sent 8% to a person. Its average reward, 0.875, matched always using the stacked model, 0.876: its value is in choosing which cases to hand over, not in better answers.

    Share of complaints in offline tests

    Who handles each complaint
    Item Share
    Handled automatically 92%
    Sent to a person 8%

Numbers from the project's public repository.

The question

Every complaint a bank receives has to be read, matched to the product and the rules it concerns, and sent to the right place. Can a system do that for the clear cases, show its evidence, and hand the unclear ones to a person? This project answers with the CFPB’s public database of US consumer complaints, sorted into ten product categories.

Cleaning before modelling

The database holds 13,906,793 complaints; 3,740,313 include the consumer’s own description. A third of the descriptions were exact duplicates: resubmissions and template complaints from law firms. Left in, the same text could land in both training and test and inflate every score, so duplicates were removed before splitting, leaving 2,474,632. The split is by time: training on complaints from before 2025, testing on 274,065 from mid-2025 onwards.

The categories are badly imbalanced. Credit reporting is 52.7% of the training data; debt management is 0.1%, just 1,846 complaints.

A baseline that looked better than it was

A logistic-regression baseline on TF-IDF features scored a macro-F1 of 0.754 on a sample of 100,000 complaints. Trained and tested on the full data, it scored 0.689. The sample had made it look competitive, so every later model is compared against 0.689.

Models

  • Two transformers on one laptop GPU. RoBERTa and ModernBERT were fine-tuned on an 8 GB card. Tokenising the data once, up front, cut the estimated training time from 97 hours to 27.
  • One change at a time. An ablation tested each training setting on its own; only a longer input of 384 tokens helped, and it was kept for both models.
  • Distillation. A 7-billion-parameter teacher collapsed to a single class for most inputs, so a smaller model built for zero-shot classification, BART-large-MNLI, labelled 25,000 complaints with soft probabilities. Trained on the full data with those labels, RoBERTa rose from 0.730 (standard fine-tuning on 500,000 complaints) to 0.750.
  • A stack. Logistic regression over both models’ probabilities reached 0.757.

Automatic, or a person

Two confidence checks come first: if the first model is less than 65% sure, or the combined model less than 55%, the complaint goes to a person. Above them, a Thompson-sampling bandit, trained on the outcomes of 1.8 million real complaints, picks which model’s answer to use, or a person. In offline tests it sent 8% of complaints to a person. Its average reward, 0.875, matched always using the stacked model, 0.876: its value is in choosing which cases to hand over, not in better answers.

The live app shows every step: the product, the confidence, the routing reason, and the CFPB examination procedures that apply, found with BM25 among 2,689 passages from 12 procedure documents.

Results

On 274,065 held-out complaints, the stacked model reaches a macro-F1 of 0.757, against 0.689 for the baseline. 13.8% of complaints are misclassified, and the errors are systematic: three pairs of categories account for 39.5% of them, led by debt-collection complaints labelled as credit reporting, because consumers describe the damage to their credit report. Debt management stays the hardest category, at about 35% F1.

Limits

  • Some wrong answers come with very high confidence, above 0.99, so confidence alone cannot catch every error.
  • The model cannot say a complaint is outside its ten products. An off-topic message still gets a product, sometimes with high confidence, and can be handled automatically.
  • The bandit learned from past outcomes and a yes-or-no reward; it would need retraining as complaints change.
  • Retrieval is keyword-based, to keep the free server’s start-up short, so it misses passages that use different words.
  • The free server sleeps when idle; the app shows stored results while it wakes.

Built with

  • PyTorch
  • RoBERTa
  • ModernBERT
  • BM25
  • Gradio
  • React
  • Cloudflare

Complaint data from the CFPB Consumer Complaint Database.

Next project PetroKnowledge