Instructions to use arunp0/nandi-grievance-indic with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use arunp0/nandi-grievance-indic with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="arunp0/nandi-grievance-indic")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModel tokenizer = AutoTokenizer.from_pretrained("arunp0/nandi-grievance-indic") model = AutoModel.from_pretrained("arunp0/nandi-grievance-indic", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Nandi: complaint router for Indian languages and Hinglish (category, top 3, experimental urgency)
Nandi (nandi-grievance-indic) is an open-weights, Apache-2.0 model that reads a customer complaint in Hindi, Hinglish, 10
other Indian languages or English and returns one of 53 categories, the top 3 categories with probabilities, and an
urgency level (P1/P2/P3). The categories follow the complaint lists of India's regulators and grievance systems:
RBI (banking), IRDAI (insurance), CPGRAMS (central government services), TRAI/DoT (telecom) and the National Consumer
Helpline, plus "not a complaint". It is a 560M-parameter XLM-RoBERTa-large encoder with two small heads.
| On 443 real public posts it never saw, labelled blind | Category accuracy | Top-3 accuracy | Macro-F1 |
|---|---|---|---|
| Nandi | 0.625 | 0.813 | 0.428 |
| Qwen3.6-35B-A3B zero-shot (about 60ร larger) | 0.664 | โ | 0.624 |
| Yama zero-shot (same encoder size) | 0.366 | โ | 0.283 |
Read this before you use it.
- Category: usable with a human in the loop. On real posts it picks the right category about 6 times in 10 and has it in the top 3 about 8 times in 10. A large zero-shot LLM is somewhat more accurate and much better on rare categories; this model's case is that it is small, local and fast.
- Urgency: experimental on real text. It catches only about 1 in 4 real P1 (most urgent) complaints, and 1 in 10 if you leave out loan-app harassment. Do not use it to decide what gets handled first without a human check.
- About 1 in 5 real posts that are not complaints get routed to a category anyway.
- All training data is synthetic. Details in Evaluation and Limitations.
Key facts
- What: a text classifier for complaint and grievance routing, with a Python
route()API. - Languages: Hindi, Hinglish, Bengali, Tamil, Telugu, Marathi, Gujarati, Kannada, Malayalam, Punjabi, Urdu, Odia and Indian English, in native scripts and romanised. The real-text test is mostly English, Hindi and Hinglish.
- Returns: category, domain, confidence, urgency with probabilities, and the top 3 categories.
- Runs: locally on CPU or GPU, no API calls; Apache-2.0, free for commercial use.
The name. In Shiva temples, devotees whisper their wishes into the ear of Nandi, the bull who keeps Shiva's gate; Nandi does not grant the wish but makes sure it reaches the right place. This model does the same with complaints: it does not resolve them, it sends each one to the right desk. It is a companion to Yama, a zero-shot classifier, and Chitragupta, a PII detector, for the same languages.
Nandi is an independent personal release.
| Parameters | about 560M (XLM-RoBERTa-large, mean pooling, a 53-way category head and a 3-way urgency head) |
| Input | up to 512 tokens (longer texts are cut) |
| Training | 3 epochs over 109,776 synthetic complaints and non-complaints in 13 languages |
| Licence | Apache-2.0 |
Quickstart
# pip install torch transformers safetensors huggingface_hub
import sys
from huggingface_hub import snapshot_download
path = snapshot_download("arunp0/nandi-grievance-indic")
sys.path.insert(0, path)
from router import ComplaintRouter
r = ComplaintRouter(path) # GPU if available
r.route("UPI se 2,000 rupaye kat gaye par receiver ko nahi mile, aaj subah ki baat hai. Bank helpline koi jawab nahi de raha.")
# {'category': 'bank.digital_banking', 'domain': 'bank', 'confidence': 0.9999, 'urgency': 'P1',
# 'urgency_probs': {'P1': 0.868, 'P2': 0.0012, 'P3': 0.1307},
# 'top3': [('bank.digital_banking', 0.9999), ('bank.fraud_unauthorised_txn', 0.0), ('con.ecommerce', 0.0)]}
r.route("Thank you SBI for the quick help at the Andheri branch today!")
# {'category': 'none.out_of_scope', 'domain': 'none', ..., 'urgency': 'P3', ...}
r.route_batch(list_of_texts) # batched
router.py is the supported way to run the model; taxonomy.py holds the 53 categories, their descriptions and
their mapping to the official lists. Confidence values are not calibrated: the model is often 0.99+ sure when it is
wrong on real text, so do not use confidence alone to skip human review. Use the top 3 for routing suggestions.
The evaluation numbers were computed on GPU in bfloat16 (the blind real set with a float16 copy of these weights);
the published float32 weights on CPU can differ on a few borderline texts (29 of 30 identical in a spot check).
Categories
Six domains: banking and payments (RBI) 14 categories, insurance (IRDAI) 12, central government services
(CPGRAMS) 17, telecom 4, consumer 5, and none.out_of_scope (praise, plain questions, suggestions, chit-chat, news, opinions
and other posts with no service problem). The full list, with how each category is grounded, is at the
end of this card.
Grounding tiers. form (23): a verbatim item on an official complaint form (RBI Integrated Ombudsman Scheme,
IRDAI). cpgrams (17): a published CPGRAMS ministry category. nch (4): a sector named by the National Consumer
Helpline. derived (4): an official bucket split because routing or urgency differs (for example fraud split out of
digital banking). inferred (5): no published category name, defined here (railways, ration/PDS, telecom billing,
telecom other, coaching). The category names belong to the regulators and are cited as facts; this schema is a derived
mapping, not an official one.
Boundary choices (decided before the final test):
- Fraud beats the payment method: a fraudulent UPI debit is
bank.fraud_unauthorised_txn, notbank.digital_banking. - Insurance mis-sold by a bank is
bank.misselling_parabanking; by an insurer or agent,ins.misselling_ufbp. - A government hospital or PM-JAY/CGHS problem is
gov.health_hospitals; an insurer's health claim isins.health_claim. - A review criticising an app or service the writer used is a complaint even if it asks for nothing.
- Not complaints: news or information posts, live traffic or event updates, opinions about a policy, and reports
of a crime by someone else or requests asking the police to act against someone. None of the grievance systems
above handles crime, and there is no police category. Misconduct or bribery by officials is a complaint
(
gov.corruption_misconduct). - If a message has several problems, the most time-critical one wins.
Urgency
| Level | Meaning |
|---|---|
| P1 | Money lost to fraud, an unauthorised debit or a failed transfer within the last ~3 days; health or safety at risk now (patient waiting on cashless approval, gas leak, unsafe on a train); threats or abuse by recovery agents or loan apps; a complete outage now; someone stranded now |
| P2 | A short official turnaround (up to a few weeks) has been missed, or the provider ignored earlier complaints or missed its own promised date |
| P3 | Routine; no short deadline. Every non-complaint is P3 |
The levels are loosely based on published turnaround times (RBI, IRDAI, TRAI, CPGRAMS) as of 2026. The model outputs a level, never a deadline. This is not legal advice. On real text the urgency head is experimental; see Evaluation.
Training data
All training text is synthetic. No real complaints, production data or customer data were used for training.
The training and test data are public: arunp0/nandi-grievance-synthetic
(116,199 training and 2,985 test rows, Apache-2.0). In the released copy, real company names were replaced with
placeholders such as Bank A or Telco B after training; on that test set this model scores 0.932 accuracy and 0.912
macro-F1 (0.931 and 0.911 on the original text, 2,963 of 2,985 predictions identical).
- Writers: Gemma 4 26B-A4B-it (94,903 rows) and Gemma 4 31B-it (21,296 rows), both open weights under Apache-2.0. Channels: forum posts, emails, social posts, chat messages, app-store reviews, ombudsman forms, formal letters, tweets and helpline-call transcripts. Lengths from one line to about 150 words.
- Languages: Hinglish 22,047, English 16,422, Hindi 14,997, and 5,029โ8,019 each for Marathi, Bengali, Tamil, Telugu, Urdu, Malayalam, Gujarati, Kannada, Odia and Punjabi.
- Labels. In the first rounds, code chose the scenario (category, timing facts, history) and Gemma wrote the text; Qwen3.6-35B-A3B (Apache-2.0) then labelled each text blind, and rows where it disagreed were dropped. Later rounds wrote freer text, closer to real reviews and tweets, and labelled it with two open-weight teachers (Gemma 4 31B and Qwen3.6); only rows where both agreed on the category, and did not disagree about P1, were kept. So labels are "scenario-designed or teacher-labelled, LLM-written, cross-checked by a second model".
- 15,902 rows are non-complaints; 24,638 are P1.
- Every person, account, phone or policy number in training text is a dummy value.
- No output of a proprietary model is in the training data. Claude (Anthropic) was used only to build and label evaluation sets, never for training data.
Evaluation
Real text (headline)
No openly licensed set of real Indian complaints with categories exists, so three private sets of real public posts were built. They are never trained on or released; only aggregate scores are reported.
Blind set (443 posts) from public Play Store and App Store India reviews and a public HR forum, collected read-only after the model was frozen. 38% are not English (mostly Hindi and Hinglish; other Indian languages are thin). Apps and companies used in the two earlier real sets were excluded. Three independent Claude agents labelled every post blind from the text alone, using the same written definitions the training teachers used; 2-of-3 votes decide. The labels are LLM-reviewed, not human-verified, and the three labellers are the same model, so their 91% agreement overstates how reliable the labels are. The evaluation plan and the decision rules were written down before any post was collected, and the set was scored once.
| Nandi | Qwen3.6 zero-shot | Yama zero-shot | |
|---|---|---|---|
| Category accuracy | 0.625 | 0.664 | 0.366 |
| Category macro-F1 | 0.428 | 0.624 | 0.283 |
| Top-3 accuracy | 0.813 | โ | โ |
| Domain accuracy | 0.749 | 0.797 | 0.497 |
| Non-complaints routed to a category (62) | 21.0% | 22.6% | 54.8% |
| P1 recall (75) | 0.25 [0.17, 0.36] | 0.20 | 0.69 |
| P1 recall, loan-app harassment (35) | 0.43 [0.28, 0.59] | 0.31 | 0.66 |
| P1 recall, all other P1 (40) | 0.10 [0.04, 0.23] | 0.10 | 0.73 |
| P1 precision | 0.70 | 0.65 | 0.32 |
Brackets are Wilson 95% intervals. 433 of the 443 posts have no product overlap with the earlier sets; scores on them are within 0.01 of the table.
- The accuracy gap to Qwen (3.9 points) is not statistically clear on this set (McNemar p โ 0.15 before the correction below). The macro-F1 gap comes from rare categories, where Nandi is weaker.
- Yama flags far more posts as P1, so its recall is high and its precision low.
- Correction, disclosed. The labelling guide wrongly told labellers to put recovery-agent threats under
bank.loan_advancesinstead of the taxonomy'sbank.recovery_harassment. On those 35 posts either category is accepted. Before this correction Nandi scored 0.571 accuracy / 0.397 macro-F1 (Qwen 0.607 / 0.593, Yama 0.352 / 0.256). Urgency numbers are unaffected.
Urgency on real text
Real urgent posts rarely state their timing the way the training scenarios do, and both Nandi and Qwen miss most of them. Treat the urgency output as a hint, not a triage decision. The labellers also read "a failed transfer" to include recent merchant payments that were debited but not credited (recharges, tickets, tolls), which the model mostly calls P3.
Earlier real sets (used to choose between model versions, so not blind): on 240 public reviews and complaint-forum posts, category accuracy 0.696 (Qwen 0.738); on a second set of 154 Play Store reviews, 0.656.
Public tweets (development set). The Mendeley "Enhanced Dataset of Citizen Centric Complaints and Grievances on Twitter" (Agarwal, Mittal and Sureka, IIIT-Delhi; CC BY 4.0) has English tweets to Indian Railways, the Income Tax Department, Delhi Police and Delhi Traffic Police. 2,249 tweets were relabelled with this taxonomy by Claude agents, with the official @handles masked because they give the category away. Error examples were read while choosing between versions, so this is a development set, not a test. Category accuracy 0.668 and top-3 0.806 on 1,198 complaints; 40% of 1,051 non-complaints routed to a category; P1 recall 26/43.
Synthetic test
2,985 complaints in 13 languages written by a different model from the training writer (Qwen3.6), with held-out channels (call transcripts, portal forms) and personas, checked by Gemma.
| Nandi | Qwen3.6 zero-shot | Yama zero-shot | TF-IDF + logistic regression | |
|---|---|---|---|---|
| Category macro-F1 | 0.909 | 0.888 | 0.536 | 0.604 |
| Category accuracy | 0.929 | 0.913 | 0.543 | 0.597 |
| Top-3 accuracy | 0.971 | โ | โ | 0.788 |
| P1 recall (rows whose P1 label a blind checker confirmed) | 0.955 | 0.904 | 0.925 | 0.540 |
| Non-complaints routed to a category | 7.3% | 5.2% | 42.9% | 0.0% |
Per-language category accuracy: English 0.98, Hinglish 0.97, Marathi 0.97, Hindi 0.96, Gujarati 0.94, Telugu 0.91, Kannada 0.91, Punjabi 0.91, Malayalam 0.90, Bengali 0.90, Tamil 0.88, Odia 0.88, Urdu 0.87.
The synthetic test follows the same definitions as training, so it overstates real-world accuracy; the real-text numbers above are the ones to plan with. The 625 test rows that the checker disagreed with (the contested slice) were not scored for this version.
Pre-registered rules
Before any data was generated: ship only if, on the synthetic test, P1 recall (checked) โฅ 0.90, macro-F1 โฅ Yama + 0.10 and non-complaint false alarms โค 10%. Passed (0.955; 0.909 vs 0.636; 7.3%). Before the blind real set was collected: the category head ships only if its macro-F1 is โฅ Yama + 0.10 there; urgency is called reliable only if the lower end of its P1-recall interval is โฅ 0.50 overall, and (added while the set was being labelled, before scoring) also without loan-app posts. Category passed, but not by a wide margin: 0.428 vs 0.383 (0.397 vs 0.356 before the correction); the margin over Yama is 0.141 before the correction with a bootstrap 95% interval of [0.07, 0.24], and 89% of resamples clear the 0.10 bar. Urgency did not pass, so it is marked experimental.
Limitations
- Synthetic training data. Real complaints are shorter, vaguer and messier. Accuracy drops from 0.93 on the synthetic test to about 0.63 on real posts.
- Urgency is experimental on real text (above).
- Non-complaints. About 1 in 5 real non-complaints (praise, questions, opinions) get a complaint category; on public tweets it was about 2 in 5. Attempts to tighten this cost recall on real traffic and transport complaints, so they were not shipped.
- Rare categories are weak on real text (macro-F1 0.43).
- Confidence is not calibrated: high confidence does not mean the answer is right.
- Real-text evaluation is English, Hindi and Hinglish heavy. Other Indian languages are tested only on synthetic text.
- Labels for every real-text number are LLM-reviewed, not human-verified.
- Category names and turnaround times are as of 2026; regulators change their lists. Not legal advice.
- It routes; it does not resolve complaints or decide whether one is valid.
Files
| File | What |
|---|---|
model.safetensors, config.json |
XLM-RoBERTa-large encoder weights |
heads.safetensors |
category and urgency heads |
router_config.json |
category and urgency order, max length |
tokenizer.json, tokenizer_config.json |
tokenizer |
router.py |
ComplaintRouter with route() / route_batch() |
taxonomy.py |
the 53 categories, descriptions, grounding tiers and official mappings |
LICENSE, NOTICE |
Apache-2.0; XLM-RoBERTa MIT notice |
Citation
@misc{prabhakaran2026nandi,
author = {Arun Prabhakaran},
title = {Nandi: complaint routing for Indian languages and Hinglish},
year = {2026},
url = {https://huggingface.co/arunp0/nandi-grievance-indic}
}
The development tweets come from: Agarwal, Mittal and Sureka (IIIT-Delhi), Enhanced Dataset of Citizen Centric Complaints and Grievances on Twitter, Mendeley Data, https://data.mendeley.com/datasets/w2cp7h53s5 (CC BY 4.0).
Licence
Apache-2.0. Copyright 2026 Arun Prabhakaran. The encoder is fine-tuned from FacebookAI/xlm-roberta-large (MIT); see
NOTICE.
All 53 categories
| Category | Covers | Tier | Official mapping |
|---|---|---|---|
bank.atm_debit_card |
ATM cash not dispensed, debit card blocked, card charges or replacement | form | RB-IOS form i ATM/Debit Cards |
bank.digital_banking |
UPI, IMPS, NEFT, net or mobile banking failure, pending or failed transfer, wallet problems | form | RB-IOS form v Internet/Mobile/Electronic Banking |
bank.fraud_unauthorised_txn |
Unauthorised or fraudulent debit, phishing, SIM swap, card skimming, scam payment | derived | split of RB-IOS i/iii/v; CPGRAMS DFS-Banking 'Fraud' |
bank.credit_card |
Credit card billing, charges, limits, closure, disputed card transactions | form | RB-IOS form iii Credit Cards |
bank.loan_advances |
Loan sanction or disbursal, interest or EMI, foreclosure, return of property documents, NBFC or app loans | form | RB-IOS form vi Loans and advances |
bank.recovery_harassment |
Recovery agents, coercive collection calls or visits, Fair Practices Code breaches | derived | split of RB-IOS vi (recovery agents, harassment, FPC) |
bank.deposit_account |
Savings or current account opening, operation, closure, charges, minimum balance, interest, locker | form | RB-IOS form iv Deposit Account |
bank.kyc_freeze |
KYC update problems, account frozen or debit-frozen | form | RB-IOS form xi KYC updation |
bank.remittance_cheque |
Cheque, demand draft or bill collection, inward or outward remittance | form | RB-IOS form xiv Remittance and collection |
bank.pension_senior |
Pension credited by a bank, arrears, facilities for senior citizens | form | RB-IOS form x Pensions and facilities for Senior Citizens |
bank.misselling_parabanking |
Insurance, mutual fund or other third-party product sold by a bank, mis-selling | form | RB-IOS form viii Para-Banking, ix Mis-selling |
bank.staff_behaviour |
Rude, discriminatory or corrupt behaviour by bank staff | form | RB-IOS form xiii Staff behaviour |
bank.credit_report |
Credit score or credit bureau report errors | derived | RBI annual report CIC rows |
bank.other |
Other banking: notes and coins, guarantees, premises, government business, access for differently abled | form | RB-IOS form ii, vii, xii, xv, xvi |
ins.death_claim |
Life insurance death claim delayed or rejected | form | IRDAI life: Death Claims |
ins.survival_maturity |
Survival benefit, maturity or annuity payout delayed or wrong | form | IRDAI life: Survival Claims |
ins.health_claim |
Health insurance cashless denial or delay, reimbursement rejected or partly paid | form | IRDAI life: Health Claims; non-life: Claims x Health |
ins.motor_claim |
Motor insurance own-damage or third-party claim, survey delay | form | IRDAI non-life: Claims x Motor |
ins.property_crop_other_claim |
Fire, crop, travel or other non-life claims, surveyor or loss assessment | form | IRDAI non-life: Claims x Fire/Crop/Misc |
ins.policy_servicing |
Surrender, policy loan, nominee or address change, revival, policy document not received | form | IRDAI life: Policy Servicing; non-life: Policy Related |
ins.proposal_processing |
Proposal pending or rejected, cover note not issued | form | IRDAI: Proposal Processing; Cover Note |
ins.misselling_ufbp |
Policy mis-sold by an insurer or agent, unfair business practice, fake bonus calls | form | IRDAI life: Unfair Business Practices; non-life: Product/Sales related |
ins.ulip |
ULIP fund value, switch or NAV problems | form | IRDAI life: ULIP Related |
ins.premium_refund |
Premium wrongly debited, refund or free-look cancellation not paid | form | IRDAI non-life: Premium; Refund |
ins.coverage_policy_terms |
Dispute about what the policy covers or its terms | form | IRDAI non-life: Coverage |
ins.other |
Other insurance complaints | form | IRDAI: Others |
gov.pf_epfo |
PF withdrawal, settlement, transfer, member detail correction, employer not depositing PF | cpgrams | MoLBR PF categories |
gov.pension |
EPS, civil or defence pension from a government body | cpgrams | MoLBR Pension (EPS); uniform 'Pension Related' |
gov.employee_service |
Pay, allowances and service matters of government employees | cpgrams | uniform 'Employee Services Related'; Defence Finance 'Pay & Allowance' |
gov.agri_pmkisan |
PM-KISAN instalments stopped or not received, land record approval | cpgrams | DOAAC PM-KISAN categories |
gov.housing_pmay |
PMAY housing benefit or HUDCO | cpgrams | DOURD PMAY; MoHUA HUDCO |
gov.income_tax_pan |
Income tax refund, wrong demand, PAN problems | cpgrams | CBDT Refund/Wrong Demand; PAN Issues |
gov.aadhaar |
Aadhaar update or enrolment problems | cpgrams | UIDAI Non Updation of AADHAAR |
gov.passport |
Passport application or police verification delay | cpgrams | MEA Passport Application Related |
gov.postal |
Postal article delayed, not delivered or lost, post office savings | cpgrams | DPOST Delay/Non-Delivery/Abstraction |
gov.railways |
Train ticket, refund, onboard service or station problems | inferred | Railways (category names not published) |
gov.roads_transport |
Road condition, driving licence or vehicle registration, transport | cpgrams | MoRTH Roads; Transport Related |
gov.health_hospitals |
Government hospital, CGHS or PM-JAY treatment denied or poor | cpgrams | DHLTH Hospitals |
gov.lpg_fuel |
LPG booking, delivery, agency or short weight | cpgrams | MoPNG LPG/LPG Agency Related |
gov.ration_pds |
Ration card or public distribution supply | inferred | Food & Public Distribution (names not published) |
gov.cooperative |
Cooperative society or cooperative bank disputes | cpgrams | MoCOP Central Registrar and Cooperative Society |
gov.corruption_misconduct |
Bribery, misbehaviour or harassment by government officials | cpgrams | uniform 'Misbehaviour/Corruption/Harassment' |
gov.other_scheme |
Other government scheme benefits or citizen services | cpgrams | uniform 'Government Schemes Related' / 'Citizen Services Related' |
tel.network_coverage |
No signal, call drops, network outage | cpgrams | DoT Issues Related to Network Coverage |
tel.broadband_internet |
Mobile data or broadband speed or outage | cpgrams | DoT Broadband Related |
tel.billing_recharge |
Wrong bill, recharge not credited, unwanted value-added service charges | inferred | TRAI billing complaints (no official category list) |
tel.other |
SIM, porting, spam calls or other telecom problems | inferred | Telecom (no official category list) |
con.ecommerce |
Online order not delivered, wrong or defective item, refund or return refused | nch | NCH sector E-commerce |
con.durables_electronics |
Appliance or electronics defect, warranty, service centre | nch | NCH sectors Consumer Durables, Electronics Products |
con.travel_airlines |
Flight, travel or hotel refund and cancellation | nch | NCH sector Travel & Tourism |
con.education_coaching |
Coaching or edtech refund, service not delivered | inferred | PIB NCH coaching refund cases |
con.other |
Other consumer complaints: food, retail, automobile, real estate, electricity | nch | NCH: Food, Retail Outlets, Direct Selling, Agency Services, Others |
none.out_of_scope |
Not a routable grievance: a question, praise, spam, court matter, employer-employee dispute | derived | RB-IOS cl. 10(2) exclusions; CPGRAMS exclusions |
- Downloads last month
- 35
Model tree for arunp0/nandi-grievance-indic
Base model
FacebookAI/xlm-roberta-largeDataset used to train arunp0/nandi-grievance-indic
Evaluation results
- Category accuracy on Real public posts, blind set (443 rows, private, LLM-reviewed labels)self-reported0.625
- Top-3 accuracy on Real public posts, blind set (443 rows, private, LLM-reviewed labels)self-reported0.813
- Category macro-F1 on Real public posts, blind set (443 rows, private, LLM-reviewed labels)self-reported0.428
- Category macro-F1 on Synthetic test (2,985 rows, 13 languages, different writer model)self-reported0.909
- Category accuracy on Synthetic test (2,985 rows, 13 languages, different writer model)self-reported0.929